diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5d463d8bcf..fe0d820eca 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,7 +14,7 @@ Here are the guidelines we'd like you to follow: ## Got a Question or Problem? -If you have questions about how to contribute to runelite, please join our [Discord](https://discord.gg/mePCs8U) server. +If you have questions about how to contribute to runelite, please join our [Discord](https://discord.gg/HN5gf3m) server. ## Found an Issue? @@ -43,7 +43,7 @@ quickly: ### Submitting a Pull Request Before you submit your pull request consider the following guidelines: -* Search [GitHub](https://github.com/runelite/runelite/pulls) for an open or closed Pull Request +* Search [GitHub](https://github.com/runelite-extended/runelite/pulls) for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort. * [Fork](https://help.github.com/articles/fork-a-repo/) this repo. * [Clone](https://help.github.com/articles/cloning-a-repository/) your copy. @@ -54,7 +54,7 @@ Before you submit your pull request consider the following guidelines: * After cloning, set a new remote [upstream](https://help.github.com/articles/configuring-a-remote-for-a-fork/) (this helps to keep your fork up to date) ```shell - git remote add upstream https://github.com/runelite/runelite.git + git remote add upstream https://github.com/runelite-extended/runelite.git ``` * Make your changes in a new git branch: @@ -130,5 +130,5 @@ from the main (upstream) repository: To ensure consistency throughout the source code, review our [code conventions](https://github.com/runelite/runelite/wiki/Code-Conventions). -[github]: https://github.com/runelite/runelite -[discord]: https://discord.gg/mePCs8U +[github]: https://github.com/runelite-extended/runelite +[discord]: https://discord.gg/HN5gf3m diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..0c53255148 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +patreon: RuneLitePlus diff --git a/.gitignore b/.gitignore index a4f1ba7d02..3ce4787a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,15 @@ project.properties .idea/ .project .settings/ -.classpath \ No newline at end of file +.classpath +runelite-client/src/main/resources/META-INF/MANIFEST.MF +git +classes/artifacts/client_jar/run.bat +classes/artifacts/client_jar/client.jar +*.jar +.live/ +**/build/* +*/out/ +.gradle/ +runelite-client/src/main/resources/runelite/* +.staging/ \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config deleted file mode 100644 index 67bd169f31..0000000000 --- a/.mvn/jvm.config +++ /dev/null @@ -1 +0,0 @@ --Xmx512m diff --git a/.travis.yml b/.travis.yml index c33e2a950c..885fd45c3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,17 @@ -language: java sudo: false -dist: xenial +dist: trusty + +language: java +jdk: oraclejdk8 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ cache: directories: - $HOME/.m2 -jdk: -- openjdk8 -- openjdk11 -install: true -script: ./travis/build.sh -notifications: - irc: - channels: - # "irc.rizon.net#runelite" - - secure: "mWQqHYRviyGWM5qQmQ3wtfUg2kFPNwC3ISbqsZhQiHiMLBxZoONMQVHIFgc09h1EqnM1mOLDAh6TSxsxNW1FR7j70pWc3hPOu8Lk1iu1vzLs/RN4dgKH8eQ0u1CpB5oaqHqC3X2CFGrytd92M7GUzHencY0twsze8m0592XRyK/3cuQwCRHFKN0uI7KNqF6n0M3/335sivB8gfy/zy5nKQmgL+r7Z2p4VrrUrSvdM3hKGhbixHijNB+5LONJpRpc/Z2Hgb5H5zmEcHBPRkHakmn5XtPYaC6eFqENi4lh99HYsN9q+ifarV0uQBdXNtJt0BFPFk0CnsnHD9QHRYOiRxROI7axISXmzA+3uoYuIMkwEcHivMkWXbgRLZaMvkjuIBKaKypTvVm+zWEPAdsNfCrWrqBuAwac6L/D++cri4f+00dKMXSE4TYSUV5mQj2WFBZ3se37UJGns4NHhcDe8SHoy6vnmIwaqGoylMmeTO80EVfFzkZxjH/YrRdwsuyCeQG9Tgv0FIDkhxwpsY/ek/TE/SHgO5U5gtKpFfB0cFc5RWfXxKat7PI31Ln5r5XahizScUi0FcdVD1cbGWug5/2vG0AEkyrgyFRJZuSxjCVidrX6vLitCNLGVvMC+BpZBSiDVZvyeQJ10SIwZnKDF+SlgSNv2+aEWnhunZ6GBb8=" + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + +script: ./gradlew build + diff --git a/README.md b/README.md index 2c0c749e1f..2aa8ec6f1e 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,56 @@ -![](https://runelite.net/img/logo.png) -# runelite [![Travis](https://img.shields.io/travis/runelite/runelite.svg)](https://travis-ci.org/runelite/runelite) [![Discord](https://img.shields.io/discord/301497432909414422.svg)](https://discord.gg/mePCs8U) +![](https://i.imgur.com/OVRdQBz.png) -RuneLite is a free, open source OldSchool RuneScape client. -If you have any questions, please join our IRC channel on [irc.rizon.net #runelite](http://qchat.rizon.net/?channels=runelite&uio=d4) or alternatively our [Discord](https://discord.gg/mePCs8U) server. + +# RuneLitePlus +[![Build Status](https://travis-ci.org/runelite-extended/runelite.svg?branch=master)](https://travis-ci.org/runelite-extended/runelite) +[![HitCount](http://hits.dwyl.io/runelite-extended/runelite.svg)](http://hits.dwyl.io/runelite-extended/runelite) +[RuneLitePlus](https://runelitepl.us) is an extended version of [RuneLite](https://github.com/runelite/runelite) that provides more functionality and less restrictions while staying more open-source. We are not affiliated with RuneLite. + +## Discord +[![Discord](https://img.shields.io/discord/373382904769675265.svg)](https://discord.gg/HN5gf3m) ## Project Layout - [cache](cache/src/main/java/net/runelite/cache) - Libraries used for reading/writing cache files, as well as the data in it -- [http-api](http-api/src/main/java/net/runelite/http/api) - API for api.runelite.net -- [http-service](http-service/src/main/java/net/runelite/http/service) - Service for api.runelite.net +- [deobfuscator](deobfuscator/src/main/java/net/runelite/deob) - Can decompile and cleanup gamepacks as well as map updates to newer revs +- [http-api](http-api/src/main/java/net/runelite/http/api) - API for runelite and runeliteplus +- [http-service](http-service/src/main/java/net/runelite/http/service) - Service for https://api.runelite.net +- [http-service-plus](http-service-plus/src/main/java/net/runelite/http/service) - Service for https://api.runelitepl.us +- [injector-plugin](injector-plugin/src/main/java/net/runelite/injector) - Tool for implementing our modifications to the gamepack - [runelite-api](runelite-api/src/main/java/net/runelite/api) - RuneLite API, interfaces for accessing the client +- [runelite-mixins](runelite-mixins/src/main/java/net/runelite) - Classes containing the Objects to be injected using the injector-plugin +- [runescape-api](runescape-api/src/main/java/net/runelite) - Mappings correspond to these interfaces, runelite-api is a subset of this - [runelite-client](runelite-client/src/main/java/net/runelite/client) - Game client with plugins -## Usage +## Building +We have migrated the project to Gradle. Information on how to setup and build the project can be found at https://github.com/runelite-extended/runelite/wiki/Building-with-IntelliJ-IDEA -Open the project in your IDE as a Maven project, build the root module and then run the RuneLite class in runelite-client. -For more information visit the [RuneLite Wiki](https://github.com/runelite/runelite/wiki). +## Private Servers +Currently we support RSMod which can be found at: https://github.com/Tomm0017/rsmod but should work with any server that follows osrs protocol. +``` +Gradle +enter +run --args='--rs=RSPS' +into the tasks box -### License +Jar +Use -rs=RSPS arg. -RuneLite is licensed under the BSD 2-clause license. See the license header in the respective file to be sure. +Exe +Use --clientargs="--rs=RSPS" arg. + +----- + +Codebase is set to 127.0.0.1 by default and can be changed in the Private Server plugin (requires restart) +Update modulus in Private Server plugin. + +Disable Default World plugin if you have it enabled, this causes issues with codebase. +``` +## License + +RuneLitePlus is licensed under the BSD 2-clause license. See the license header in the respective file to be sure. ## Contribute and Develop -We've set up a separate document for our [contribution guidelines](https://github.com/runelite/runelite/blob/master/.github/CONTRIBUTING.md). +We've set up a separate document for our [contribution guidelines](https://github.com/runelite-extended/runelite/blob/master/.github/CONTRIBUTING.md). diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..175278cab0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,167 @@ +import org.ajoberstar.grgit.Grgit + +buildscript { + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.ajoberstar.grgit:grgit-gradle:3.1.1" + } +} + +plugins { + id "com.github.ben-manes.versions" version "0.22.0" apply false + id "com.gradle.build-scan" version "2.4" +} + +apply plugin: 'application' + +ext { + grgit = Grgit.open(dir: "${rootProject.projectDir}") + localGitCommit = grgit.head().id + localGitCommitShort = grgit.head().getAbbreviatedId(7) + localGitDirty = !grgit.status().clean + + // Dependencies versions + annotations = '17.0.0' + antlr = '4.7.2' + apacheCommonsCompress = '1.18' + apacheCommonsCsv = '1.7' + apacheCommonsText = '1.7' + asm = '7.1' + commonsCli = '1.4' + discord = '1.1' + fernflower = '20171017' + findbugs = '3.0.2' + gson = '2.8.5' + guava = '28.0-jre' + guice = '4.2.2' + h2 = '1.4.199' + hamcrest = '2.1' + httpcore = '4.4.11' + httpmime = '4.5.9' + javassist = '3.25.0-GA' + javaxInject = '1' + jbsdiff = '1.0' + jclCore = '2.8' + jedis = '3.1.0' + jna = '5.4.0' + jogamp = '2.3.2' + jopt = '5.0.4' + jooq = '3.11.12' + junit = '4.12' + jupiter = '5.5.1' + logback = '1.2.3' + lombok = '1.18.8' + mapstruct = '1.3.0.Final' + mariadbJdbc = '2.4.3' + mavenPluginAnnotations = '3.6.0' + mavenPluginApi = '3.6.1' + minio = '6.0.9' + mockito = '3.0.0' + mongodbDriverSync = '3.11.0' + mysqlConnectorJava = '8.0.17' + netty = '4.1.37.Final' + okhttp3 = '4.1.0' + orangeExtensions = '1.0' + petitparser = '2.2.0' + plexus = '3.2.1' + rxjava = '2.2.11' + rxrelay = '2.1.0' + scribejava = '6.7.0' + sisu = '0.3.3' + slf4j = '1.7.26' + springJdbc = '5.1.9.RELEASE' + springboot = '2.1.7.RELEASE' + sql2o = '1.6.0' + substance = '8.0.02' + trident = '1.5.00' +} + +allprojects { + apply plugin: 'maven' + apply plugin: 'checkstyle' + + group = 'net.runelite' + version = '1.5.32-SNAPSHOT' + + ext { + rsversion = 181 + cacheversion = 165 + plusVersion = '2.1.4.0' + + gitCommit = localGitCommit + gitCommitShort = localGitCommitShort + gitDirty = localGitDirty + + rootPath = rootDir.toString().replace('\\', '/') + injectedClassesPath = rootPath + "/injector-plugin/out/injected-client/" + } +} + +subprojects { + apply plugin: 'java-library' + apply plugin: 'maven' + apply plugin: "com.github.ben-manes.versions" + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + } + + tasks.withType(GroovyCompile).configureEach { + options.incremental = true + } + + repositories { + mavenLocal() + + maven { url "http://repo1.maven.org/maven2" } + maven { url "http://repo.runelite.net" } + maven { url "http://repo.maven.apache.org/maven2" } + maven { url "https://raw.githubusercontent.com/runelite-extended/maven-repo/master" } + + if (System.getenv("NEXUS-URL") != null) { + maven { url System.getenv("NEXUS-URL") } + } + } + + checkstyle { + toolVersion = '6.4.1' + sourceSets = [sourceSets.main] + configFile = rootProject.file("./checkstyle/checkstyle.xml") + configProperties = [ "suppressionFile" : rootProject.file("./checkstyle/suppressions.xml")] + showViolations = true + ignoreFailures = false + maxWarnings = 0 + } + + uploadArchives { + repositories { + mavenDeployer { + repository(url: System.getenv("NEXUS_URL")) { + authentication(userName: System.getenv("NEXUS_USER"), password: System.getenv("NEXUS_PASSWORD")) + } + } + } + } +} + +wrapper { + gradleVersion = '5.6' + + doLast { + def optsEnvVar = "DEFAULT_JVM_OPTS" + scriptFile.write scriptFile.text.replace("$optsEnvVar='\"-Xmx64m\" \"-Xms64m\"'", "$optsEnvVar='\"-Xmx4g\" \"-Xms2g\" \"-Dfile.encoding=UTF-8\"'") + batchScript.write batchScript.text.replace("set $optsEnvVar=\"-Xmx64m\" \"-Xms64m\"", "set $optsEnvVar=\"-Xmx4g\" \"-Xms2g\" \"-Dfile.encoding=UTF-8\"") + } +} + +run { + classpath = childProjects.client.sourceSets.main.runtimeClasspath + mainClassName = "net.runelite.client.RuneLite" +} diff --git a/cache-client/build.gradle b/cache-client/build.gradle new file mode 100644 index 0000000000..024ee40b90 --- /dev/null +++ b/cache-client/build.gradle @@ -0,0 +1,14 @@ +description = 'Cache Client' + +dependencies { + api project(':cache') + api project(':protocol') + + implementation group: 'com.google.guava', name: 'guava', version: guava + implementation group: 'io.netty', name: 'netty-all', version: netty + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4j + + testImplementation group: 'junit', name: 'junit', version: junit + testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: slf4j + testImplementation project(path: ':cache', configuration: 'testArchives') +} diff --git a/cache-client/pom.xml b/cache-client/pom.xml deleted file mode 100644 index 34c68a91ec..0000000000 --- a/cache-client/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.5.32-SNAPSHOT - - - cache-client - Cache Client - - - - net.runelite - cache - ${project.version} - - - net.runelite - protocol - ${project.version} - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - 1.7.12 - test - - - net.runelite - cache - ${project.version} - test-jar - test - - - diff --git a/cache-updater/build.gradle b/cache-updater/build.gradle new file mode 100644 index 0000000000..ec821898e7 --- /dev/null +++ b/cache-updater/build.gradle @@ -0,0 +1,15 @@ +description = 'Cache Updater' + +dependencies { + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok + + compileOnly group: 'org.projectlombok', name: 'lombok', version: lombok + + implementation group: 'io.minio', name: 'minio', version: minio + implementation group: 'mysql', name: 'mysql-connector-java', version: mysqlConnectorJava + implementation group: 'org.springframework.boot', name: 'spring-boot-devtools', version: springboot + implementation group: 'org.springframework.boot', name: 'spring-boot-starter', version: springboot + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc', version: springboot + implementation group: 'org.sql2o', name: 'sql2o', version: sql2o + implementation project(':cache-client') +} diff --git a/cache-updater/pom.xml b/cache-updater/pom.xml deleted file mode 100644 index c90dedc037..0000000000 --- a/cache-updater/pom.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - 4.0.0 - - net.runelite - runelite-parent - 1.5.32-SNAPSHOT - - - Cache Updater - cache-updater - - - 1.5.6.RELEASE - - - - - org.springframework.boot - spring-boot-starter - ${spring.boot.version} - - - org.springframework.boot - spring-boot-starter-jdbc - ${spring.boot.version} - - - org.springframework.boot - spring-boot-devtools - ${spring.boot.version} - true - - - mysql - mysql-connector-java - 5.1.45 - - - net.runelite - cache-client - ${project.version} - - - org.sql2o - sql2o - 1.5.4 - - - io.minio - minio - 3.0.6 - - - org.projectlombok - lombok - provided - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - spring-boot - net.runelite.cache.updater.CacheUpdater - - - - - - - \ No newline at end of file diff --git a/cache-updater/src/main/java/net/runelite/cache/updater/CacheConfiguration.java b/cache-updater/src/main/java/net/runelite/cache/updater/CacheConfiguration.java index 229ea1d268..69c405aeb4 100644 --- a/cache-updater/src/main/java/net/runelite/cache/updater/CacheConfiguration.java +++ b/cache-updater/src/main/java/net/runelite/cache/updater/CacheConfiguration.java @@ -33,8 +33,8 @@ import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.sql2o.Sql2o; diff --git a/cache-updater/src/main/java/net/runelite/cache/updater/CacheUploader.java b/cache-updater/src/main/java/net/runelite/cache/updater/CacheUploader.java index ed685a9034..5712d82645 100644 --- a/cache-updater/src/main/java/net/runelite/cache/updater/CacheUploader.java +++ b/cache-updater/src/main/java/net/runelite/cache/updater/CacheUploader.java @@ -32,6 +32,7 @@ import io.minio.errors.InsufficientDataException; import io.minio.errors.InternalException; import io.minio.errors.InvalidArgumentException; import io.minio.errors.InvalidBucketNameException; +import io.minio.errors.InvalidResponseException; import io.minio.errors.NoResponseException; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -87,7 +88,7 @@ public class CacheUploader implements Runnable minioClient.putObject(minioBucket, path, new ByteArrayInputStream(data), data.length, "binary/octet-stream"); } - catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidArgumentException | InvalidBucketNameException | NoResponseException | IOException | InvalidKeyException | NoSuchAlgorithmException | XmlPullParserException ex) + catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidArgumentException | InvalidBucketNameException | NoResponseException | IOException | InvalidKeyException | NoSuchAlgorithmException | XmlPullParserException | InvalidResponseException ex) { logger.warn("unable to upload data to store", ex); } diff --git a/cache/build.gradle b/cache/build.gradle new file mode 100644 index 0000000000..3221972040 --- /dev/null +++ b/cache/build.gradle @@ -0,0 +1,36 @@ +import org.apache.tools.ant.filters.ReplaceTokens + +plugins { + id "com.github.hauner.jarTest" version "1.0.1" +} + +description = 'Cache' + +dependencies { + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok + + api project(':http-api') + + compileOnly group: 'org.projectlombok', name: 'lombok', version: lombok + + implementation group: 'com.google.code.gson', name: 'gson', version: gson + implementation group: 'com.google.guava', name: 'guava', version: guava + implementation group: 'commons-cli', name: 'commons-cli', version: commonsCli + implementation group: 'io.netty', name: 'netty-buffer', version: netty + implementation group: 'org.antlr', name: 'antlr4-runtime', version: antlr + implementation group: 'org.apache.commons', name: 'commons-compress', version: apacheCommonsCompress + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4j + + testImplementation group: 'junit', name: 'junit', version: junit + testImplementation group: 'net.runelite.rs', name: 'cache', version: cacheversion + testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: slf4j +} + +processTestResources { + from file("src/test/resources/cache.properties"), { + filter(ReplaceTokens, tokens: [ + "rs.version": rsversion.toString(), + "cache.version": cacheversion.toString() + ]) + } +} diff --git a/cache/pom.xml b/cache/pom.xml deleted file mode 100644 index ec3139c6c1..0000000000 --- a/cache/pom.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.5.32-SNAPSHOT - - - cache - Cache - - - 165 - - 4.6 - - - - - net.runelite - http-api - ${project.version} - - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - org.apache.commons - commons-compress - 1.10 - - - com.google.code.gson - gson - - - io.netty - netty-buffer - 4.1.0.Final - - - org.antlr - antlr4-runtime - ${antlr4.version} - - - commons-cli - commons-cli - 1.3.1 - - - org.projectlombok - lombok - provided - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - 1.7.12 - test - - - net.runelite.rs - cache - ${cache.version} - test - - - - - - - src/test/resources - true - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - -Xmx2048m - - ${cache.tmpdir} - - - - - maven-assembly-plugin - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - org.antlr - antlr4-maven-plugin - ${antlr4.version} - - - process-resources - - antlr4 - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java index e77964a5ca..923f781237 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/ScriptLoader.java @@ -56,7 +56,7 @@ public class ScriptLoader int numSwitches = in.readUnsignedByte(); if (numSwitches > 0) { - Map[] switches = new Map[numSwitches]; + @SuppressWarnings("unchecked") Map[] switches = new Map[numSwitches]; def.setSwitches(switches); for (int i = 0; i < numSwitches; ++i) diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java index 15ec975d6d..3ce0bf4c3d 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java @@ -36,6 +36,7 @@ import net.runelite.cache.region.Position; public class WorldMapLoader { + @SuppressWarnings("unchecked") public WorldMapDefinition load(byte[] b, int fileId) { WorldMapDefinition def = new WorldMapDefinition(); diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/AudioEnvelopeLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/AudioEnvelopeLoader.java new file mode 100644 index 0000000000..084a8a9a86 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/AudioEnvelopeLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.loaders.sound; + +import net.runelite.cache.definitions.sound.AudioEnvelopeDefinition; +import net.runelite.cache.io.InputStream; + +public class AudioEnvelopeLoader +{ + public AudioEnvelopeDefinition load(InputStream in) + { + AudioEnvelopeDefinition audioEnvelope = new AudioEnvelopeDefinition(); + + load(audioEnvelope, in); + + return audioEnvelope; + } + + private void load(AudioEnvelopeDefinition audioEnvelope, InputStream in) + { + audioEnvelope.form = in.readUnsignedByte(); + audioEnvelope.start = in.readInt(); + audioEnvelope.end = in.readInt(); + this.loadSegments(audioEnvelope, in); + } + + final void loadSegments(AudioEnvelopeDefinition audioEnvelope, InputStream in) + { + audioEnvelope.segments = in.readUnsignedByte(); + audioEnvelope.durations = new int[audioEnvelope.segments]; + audioEnvelope.phases = new int[audioEnvelope.segments]; + + for (int i = 0; i < audioEnvelope.segments; ++i) + { + audioEnvelope.durations[i] = in.readUnsignedShort(); + audioEnvelope.phases[i] = in.readUnsignedShort(); + } + + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/InstrumentLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/InstrumentLoader.java new file mode 100644 index 0000000000..f3d2326760 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/InstrumentLoader.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.loaders.sound; + +import net.runelite.cache.definitions.sound.InstrumentDefinition; +import net.runelite.cache.definitions.sound.AudioEnvelopeDefinition; +import net.runelite.cache.io.InputStream; + +public class InstrumentLoader +{ + private final AudioEnvelopeLoader aeLoader = new AudioEnvelopeLoader(); + private final SoundEffectLoader seLoader = new SoundEffectLoader(); + + public InstrumentDefinition load(InputStream in) + { + InstrumentDefinition instrument = new InstrumentDefinition(); + + load(instrument, in); + + return instrument; + } + + private void load(InstrumentDefinition instrument, InputStream in) + { + instrument.pitch = aeLoader.load(in); + instrument.volume = aeLoader.load(in); + int volume = in.readUnsignedByte(); + if (volume != 0) + { + in.setOffset(in.getOffset() - 1); + instrument.pitchModifier = aeLoader.load(in); + instrument.pitchModifierAmplitude = aeLoader.load(in); + } + + volume = in.readUnsignedByte(); + if (volume != 0) + { + in.setOffset(in.getOffset() - 1); + instrument.volumeMultiplier = aeLoader.load(in); + instrument.volumeMultiplierAmplitude = aeLoader.load(in); + } + + volume = in.readUnsignedByte(); + if (volume != 0) + { + in.setOffset(in.getOffset() - 1); + instrument.release = aeLoader.load(in); + instrument.field1175 = aeLoader.load(in); + } + + for (int i = 0; i < 10; ++i) + { + int vol = in.readUnsignedShortSmart(); + if (vol == 0) + { + break; + } + + instrument.oscillatorVolume[i] = vol; + instrument.oscillatorPitch[i] = in.readShortSmart(); + instrument.oscillatorDelays[i] = in.readUnsignedShortSmart(); + } + + instrument.delayTime = in.readUnsignedShortSmart(); + instrument.delayDecay = in.readUnsignedShortSmart(); + instrument.duration = in.readUnsignedShort(); + instrument.offset = in.readUnsignedShort(); + instrument.filterEnvelope = new AudioEnvelopeDefinition(); + instrument.filter = seLoader.load(in, instrument.filterEnvelope); + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect1Loader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect1Loader.java deleted file mode 100644 index a5c8f16f78..0000000000 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect1Loader.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.definitions.loaders.sound; - -import net.runelite.cache.definitions.sound.SoundEffect1Definition; -import net.runelite.cache.definitions.sound.SoundEffect2Definition; -import net.runelite.cache.io.InputStream; - -public class SoundEffect1Loader -{ - private final SoundEffect2Loader se2Loader = new SoundEffect2Loader(); - private final SoundEffect3Loader se3Loader = new SoundEffect3Loader(); - - public SoundEffect1Definition load(InputStream in) - { - SoundEffect1Definition se = new SoundEffect1Definition(); - - load(se, in); - - return se; - } - - private void load(SoundEffect1Definition se, InputStream var1) - { - se.field1181 = se2Loader.load(var1); - se.field1173 = se2Loader.load(var1); - int var2 = var1.readUnsignedByte(); - if (var2 != 0) - { - var1.setOffset(var1.getOffset() - 1); - se.field1174 = se2Loader.load(var1); - se.field1193 = se2Loader.load(var1); - } - - var2 = var1.readUnsignedByte(); - if (var2 != 0) - { - var1.setOffset(var1.getOffset() - 1); - se.field1183 = se2Loader.load(var1); - se.field1192 = se2Loader.load(var1); - } - - var2 = var1.readUnsignedByte(); - if (var2 != 0) - { - var1.setOffset(var1.getOffset() - 1); - se.field1178 = se2Loader.load(var1); - se.field1175 = se2Loader.load(var1); - } - - for (int var3 = 0; var3 < 10; ++var3) - { - int var4 = var1.readUnsignedShortSmart(); - if (var4 == 0) - { - break; - } - - se.field1180[var3] = var4; - se.field1179[var3] = var1.readShortSmart(); - se.field1177[var3] = var1.readUnsignedShortSmart(); - } - - se.field1187 = var1.readUnsignedShortSmart(); - se.field1184 = var1.readUnsignedShortSmart(); - se.field1176 = var1.readUnsignedShort(); - se.field1188 = var1.readUnsignedShort(); - se.field1186 = new SoundEffect2Definition(); - se.field1182 = se3Loader.load(var1, se.field1186); - } -} diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect3Loader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect3Loader.java deleted file mode 100644 index ad7f733efb..0000000000 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect3Loader.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.definitions.loaders.sound; - -import net.runelite.cache.definitions.sound.SoundEffect2Definition; -import net.runelite.cache.definitions.sound.SoundEffect3Definition; -import net.runelite.cache.io.InputStream; - -public class SoundEffect3Loader -{ - private final SoundEffect2Loader se2Loader = new SoundEffect2Loader(); - - public SoundEffect3Definition load(InputStream in, SoundEffect2Definition var2) - { - SoundEffect3Definition se = new SoundEffect3Definition(); - - load(se, in, var2); - - return se; - } - - private void load(SoundEffect3Definition se, InputStream var1, SoundEffect2Definition var2) - { - int var3 = var1.readUnsignedByte(); - se.field1155[0] = var3 >> 4; - se.field1155[1] = var3 & 15; - if (var3 != 0) - { - se.field1156[0] = var1.readUnsignedShort(); - se.field1156[1] = var1.readUnsignedShort(); - int var4 = var1.readUnsignedByte(); - - int var5; - int var6; - for (var5 = 0; var5 < 2; ++var5) - { - for (var6 = 0; var6 < se.field1155[var5]; ++var6) - { - se.field1154[var5][0][var6] = var1.readUnsignedShort(); - se.field1159[var5][0][var6] = var1.readUnsignedShort(); - } - } - - for (var5 = 0; var5 < 2; ++var5) - { - for (var6 = 0; var6 < se.field1155[var5]; ++var6) - { - if ((var4 & 1 << var5 * 4 << var6) != 0) - { - se.field1154[var5][1][var6] = var1.readUnsignedShort(); - se.field1159[var5][1][var6] = var1.readUnsignedShort(); - } - else - { - se.field1154[var5][1][var6] = se.field1154[var5][0][var6]; - se.field1159[var5][1][var6] = se.field1159[var5][0][var6]; - } - } - } - - if (var4 != 0 || se.field1156[1] != se.field1156[0]) - { - se2Loader.method1144(var2, var1); - } - } - else - { - int[] var7 = se.field1156; - se.field1156[1] = 0; - var7[0] = 0; - } - } -} diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectLoader.java index ddb6a4d9fc..2ee518d4ee 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectLoader.java @@ -24,39 +24,70 @@ */ package net.runelite.cache.definitions.loaders.sound; +import net.runelite.cache.definitions.sound.AudioEnvelopeDefinition; import net.runelite.cache.definitions.sound.SoundEffectDefinition; -import net.runelite.cache.definitions.sound.SoundEffect1Definition; import net.runelite.cache.io.InputStream; public class SoundEffectLoader { - public SoundEffectDefinition load(byte[] b) + private final AudioEnvelopeLoader audioEnvelopeLoader = new AudioEnvelopeLoader(); + + public SoundEffectDefinition load(InputStream in, AudioEnvelopeDefinition audioEnvelope) { - SoundEffectDefinition se = new SoundEffectDefinition(); - InputStream in = new InputStream(b); + SoundEffectDefinition soundEffect = new SoundEffectDefinition(); - load(se, in); + load(soundEffect, audioEnvelope, in); - return se; + return soundEffect; } - private void load(SoundEffectDefinition se, InputStream var1) + private void load(SoundEffectDefinition soundEffect, AudioEnvelopeDefinition audioEnvelope, InputStream in) { - for (int var2 = 0; var2 < 10; ++var2) + int id = in.readUnsignedByte(); + soundEffect.pairs[0] = id >> 4; + soundEffect.pairs[1] = id & 15; + if (id != 0) { - int var3 = var1.readUnsignedByte(); - if (var3 != 0) + soundEffect.unity[0] = in.readUnsignedShort(); + soundEffect.unity[1] = in.readUnsignedShort(); + int track = in.readUnsignedByte(); + + for (int i = 0; i < 2; ++i) { - var1.setOffset(var1.getOffset() - 1); + for (int j = 0; j < soundEffect.pairs[i]; ++j) + { + soundEffect.phases[i][0][j] = in.readUnsignedShort(); + soundEffect.magnitudes[i][0][j] = in.readUnsignedShort(); + } + } - SoundEffect1Loader se1Loader = new SoundEffect1Loader(); - SoundEffect1Definition se1 = se1Loader.load(var1); + for (int i = 0; i < 2; ++i) + { + for (int j = 0; j < soundEffect.pairs[i]; ++j) + { + if ((track & 1 << i * 4 << j) != 0) + { + soundEffect.phases[i][1][j] = in.readUnsignedShort(); + soundEffect.magnitudes[i][1][j] = in.readUnsignedShort(); + } + else + { + soundEffect.phases[i][1][j] = soundEffect.phases[i][0][j]; + soundEffect.magnitudes[i][1][j] = soundEffect.magnitudes[i][0][j]; + } + } + } - se.field1008[var2] = se1; + if (track != 0 || soundEffect.unity[1] != soundEffect.unity[0]) + { + audioEnvelopeLoader.loadSegments(audioEnvelope, in); } } - - se.field1006 = var1.readUnsignedShort(); - se.field1009 = var1.readUnsignedShort(); + else + { + int[] clean = soundEffect.unity; + soundEffect.unity[1] = 0; + clean[0] = 0; + } } -} +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect2Loader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectTrackLoader.java similarity index 63% rename from cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect2Loader.java rename to cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectTrackLoader.java index d9ae3d3abb..3b991fdc70 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffect2Loader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/sound/SoundEffectTrackLoader.java @@ -24,39 +24,39 @@ */ package net.runelite.cache.definitions.loaders.sound; -import net.runelite.cache.definitions.sound.SoundEffect2Definition; +import net.runelite.cache.definitions.sound.SoundEffectTrackDefinition; +import net.runelite.cache.definitions.sound.InstrumentDefinition; import net.runelite.cache.io.InputStream; -public class SoundEffect2Loader +public class SoundEffectTrackLoader { - public SoundEffect2Definition load(InputStream in) + public SoundEffectTrackDefinition load(byte[] b) { - SoundEffect2Definition se = new SoundEffect2Definition(); + SoundEffectTrackDefinition soundEffect = new SoundEffectTrackDefinition(); + InputStream in = new InputStream(b); - load(se, in); + load(soundEffect, in); - return se; + return soundEffect; } - private void load(SoundEffect2Definition se, InputStream var1) + private void load(SoundEffectTrackDefinition soundEffect, InputStream in) { - se.field1087 = var1.readUnsignedByte(); - se.field1088 = var1.readInt(); - se.field1089 = var1.readInt(); - this.method1144(se, var1); - } - - final void method1144(SoundEffect2Definition se, InputStream var1) - { - se.field1092 = var1.readUnsignedByte(); - se.field1086 = new int[se.field1092]; - se.field1090 = new int[se.field1092]; - - for (int var2 = 0; var2 < se.field1092; ++var2) + for (int i = 0; i < 10; ++i) { - se.field1086[var2] = var1.readUnsignedShort(); - se.field1090[var2] = var1.readUnsignedShort(); + int volume = in.readUnsignedByte(); + if (volume != 0) + { + in.setOffset(in.getOffset() - 1); + + InstrumentLoader instrumentLoader = new InstrumentLoader(); + InstrumentDefinition instrument = instrumentLoader.load(in); + + soundEffect.instruments[i] = instrument; + } } + soundEffect.start = in.readUnsignedShort(); + soundEffect.end = in.readUnsignedShort(); } -} +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/sound/AudioEnvelopeDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/sound/AudioEnvelopeDefinition.java new file mode 100644 index 0000000000..46d709f6a4 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/definitions/sound/AudioEnvelopeDefinition.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.sound; + +public class AudioEnvelopeDefinition +{ + public int segments = 2; + public int[] durations = new int[2]; + public int[] phases = new int[2]; + public int start; + public int end; + public int form; + public int ticks; + public int phaseIndex; + public int step; + public int amplitude; + public int max; + + public AudioEnvelopeDefinition() + { + this.durations[0] = 0; + this.durations[1] = '\uffff'; + this.phases[0] = 0; + this.phases[1] = '\uffff'; + } + + public final int step(int var1) + { + if (this.max >= this.ticks) + { + this.amplitude = this.phases[this.phaseIndex++] << 15; + + if (this.phaseIndex >= this.segments) + { + this.phaseIndex = this.segments - 1; + } + + this.ticks = (int) ((double) this.durations[this.phaseIndex] / 65536.0 * (double) var1); + + if (this.ticks > this.max) + { + this.step = ((this.phases[this.phaseIndex] << 15) - this.amplitude) / (this.ticks - this.max); + } + } + this.amplitude += this.step; + ++this.max; + + return this.amplitude - this.step >> 15; + } + + public final void reset() + { + this.ticks = 0; + this.phaseIndex = 0; + this.step = 0; + this.amplitude = 0; + this.max = 0; + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/sound/InstrumentDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/sound/InstrumentDefinition.java new file mode 100644 index 0000000000..fafaa7b8f8 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/definitions/sound/InstrumentDefinition.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.sound; + +import java.util.Random; + +public class InstrumentDefinition +{ + public AudioEnvelopeDefinition volume; + public AudioEnvelopeDefinition pitchModifier; + public AudioEnvelopeDefinition field1175; + public AudioEnvelopeDefinition release; + public AudioEnvelopeDefinition volumeMultiplier; + public AudioEnvelopeDefinition volumeMultiplierAmplitude; + public AudioEnvelopeDefinition pitchModifierAmplitude; + public AudioEnvelopeDefinition pitch; + + public int[] oscillatorDelays = new int[] + { + 0, 0, 0, 0, 0 + }; + public int[] oscillatorPitch = new int[] + { + 0, 0, 0, 0, 0 + }; + public int[] oscillatorVolume = new int[] + { + 0, 0, 0, 0, 0 + }; + + public SoundEffectDefinition filter; + public AudioEnvelopeDefinition filterEnvelope; + + static int[] samples = new int[220500]; + + static int[] NOISE = new int[32768]; + static int[] AUDIO_SINE = new int[32768]; + + static int[] phases = new int[5]; + static int[] delays = new int[5]; + + static int[] volumeSteps = new int[5]; + static int[] pitchSteps = new int[5]; + static int[] pitchBaseSteps = new int[5]; + + public int duration = 500; + public int delayDecay = 100; + public int delayTime = 0; + public int offset = 0; + + static + { + Random random = new Random(0); + + for (int i = 0; i < 32768; ++i) + { + InstrumentDefinition.NOISE[i] = (random.nextInt() & 2) - 1; + InstrumentDefinition.AUDIO_SINE[i] = (int) (Math.sin((double) i / 5215.1903) * 16384.0); + } + } + + public final int[] synthesize(int var1, int var2) + { + int var16; + int var15; + int var14; + int var11; + int var12; + int var13; + + InstrumentDefinition.method3854(samples, 0, var1); + + if (var2 < 10) + { + return samples; + } + + double var3 = (double) var1 / ((double) var2 + 0.0); + + this.pitch.reset(); + this.volume.reset(); + + int var5 = 0; + int var6 = 0; + int var7 = 0; + + if (this.pitchModifier != null) + { + this.pitchModifier.reset(); + this.pitchModifierAmplitude.reset(); + + var5 = (int) ((double) (this.pitchModifier.end - this.pitchModifier.start) * 32.768 / var3); + var6 = (int) ((double) this.pitchModifier.start * 32.768 / var3); + } + + int var8 = 0; + int var9 = 0; + int var10 = 0; + + if (this.volumeMultiplier != null) + { + this.volumeMultiplier.reset(); + this.volumeMultiplierAmplitude.reset(); + + var8 = (int) ((double) (this.volumeMultiplier.end - this.volumeMultiplier.start) * 32.768 / var3); + var9 = (int) ((double) this.volumeMultiplier.start * 32.768 / var3); + } + + for (var11 = 0; var11 < 5; ++var11) + { + if (this.oscillatorVolume[var11] == 0) + { + continue; + } + + InstrumentDefinition.phases[var11] = 0; + InstrumentDefinition.delays[var11] = (int) ((double) this.oscillatorDelays[var11] * var3); + InstrumentDefinition.volumeSteps[var11] = (this.oscillatorVolume[var11] << 14) / 100; + InstrumentDefinition.pitchSteps[var11] = (int) ((double) (this.pitch.end - this.pitch.start) * 32.768 * Math.pow(1.0057929410678534, this.oscillatorPitch[var11]) / var3); + InstrumentDefinition.pitchBaseSteps[var11] = (int) ((double) this.pitch.start * 32.768 / var3); + } + + for (var11 = 0; var11 < var1; ++var11) + { + var12 = this.pitch.step(var1); + var13 = this.volume.step(var1); + + if (this.pitchModifier != null) + { + var14 = this.pitchModifier.step(var1); + var15 = this.pitchModifierAmplitude.step(var1); + var12 += this.evaluateWave(var7, var15, this.pitchModifier.form) >> 1; + var7 = var7 + var6 + (var14 * var5 >> 16); + } + + if (this.volumeMultiplier != null) + { + var14 = this.volumeMultiplier.step(var1); + var15 = this.volumeMultiplierAmplitude.step(var1); + var13 = var13 * ((this.evaluateWave(var10, var15, this.volumeMultiplier.form) >> 1) + 32768) >> 15; + var10 = var10 + var9 + (var14 * var8 >> 16); + } + + for (var14 = 0; var14 < 5; ++var14) + { + if (this.oscillatorVolume[var14] == 0 || (var15 = delays[var14] + var11) >= var1) + { + continue; + } + + int[] arrn = samples; + int n = var15; + arrn[n] = arrn[n] + this.evaluateWave(phases[var14], var13 * volumeSteps[var14] >> 15, this.pitch.form); + int[] arrn2 = phases; + int n2 = var14; + arrn2[n2] = arrn2[n2] + ((var12 * pitchSteps[var14] >> 16) + pitchBaseSteps[var14]); + } + } + + if (this.release != null) + { + this.release.reset(); + this.field1175.reset(); + + var11 = 0; + boolean var20 = true; + + for (var14 = 0; var14 < var1; ++var14) + { + var15 = this.release.step(var1); + var16 = this.field1175.step(var1); + var12 = var20 ? (var15 * (this.release.end - this.release.start) >> 8) + this.release.start : (var16 * (this.release.end - this.release.start) >> 8) + this.release.start; + if ((var11 += 256) >= var12) + { + var11 = 0; + } + if (!var20) + { + continue; + } + InstrumentDefinition.samples[var14] = 0; + } + } + if (this.delayTime > 0 && this.delayDecay > 0) + { + for (var12 = var11 = (int) ((double) this.delayTime * var3); var12 < var1; ++var12) + { + int[] arrn = samples; + int n = var12; + + arrn[n] = arrn[n] + samples[var12 - var11] * this.delayDecay / 100; + } + } + if (this.filter.pairs[0] > 0 || this.filter.pairs[1] > 0) + { + this.filterEnvelope.reset(); + + var11 = this.filterEnvelope.step(var1 + 1); + var12 = this.filter.compute(0, (float) var11 / 65536.0f); + var13 = this.filter.compute(1, (float) var11 / 65536.0f); + + if (var1 >= var12 + var13) + { + int var17; + + var14 = 0; + var15 = var13; + + if (var13 > var1 - var12) + { + var15 = var1 - var12; + } + + while (var14 < var15) + { + var16 = (int) ((long) samples[var14 + var12] * (long) SoundEffectDefinition.fowardMultiplier >> 16); + + for (var17 = 0; var17 < var12; ++var17) + { + var16 += (int) ((long) samples[var14 + var12 - 1 - var17] * (long) SoundEffectDefinition.coefficients[0][var17] >> 16); + } + + for (var17 = 0; var17 < var14; ++var17) + { + var16 -= (int) ((long) samples[var14 - 1 - var17] * (long) SoundEffectDefinition.coefficients[1][var17] >> 16); + } + + InstrumentDefinition.samples[var14] = var16; + var11 = this.filterEnvelope.step(var1 + 1); + ++var14; + } + var15 = 128; + + do + { + int var18; + + if (var15 > var1 - var12) + { + var15 = var1 - var12; + } + + while (var14 < var15) + { + var17 = (int) ((long) samples[var14 + var12] * (long) SoundEffectDefinition.fowardMultiplier >> 16); + + for (var18 = 0; var18 < var12; ++var18) + { + var17 += (int) ((long) samples[var14 + var12 - 1 - var18] * (long) SoundEffectDefinition.coefficients[0][var18] >> 16); + } + + for (var18 = 0; var18 < var13; ++var18) + { + var17 -= (int) ((long) samples[var14 - 1 - var18] * (long) SoundEffectDefinition.coefficients[1][var18] >> 16); + } + + InstrumentDefinition.samples[var14] = var17; + var11 = this.filterEnvelope.step(var1 + 1); + ++var14; + } + if (var14 >= var1 - var12) + { + while (var14 < var1) + { + var17 = 0; + for (var18 = var14 + var12 - var1; var18 < var12; ++var18) + { + var17 += (int) ((long) samples[var14 + var12 - 1 - var18] + * (long) SoundEffectDefinition.coefficients[0][var18] >> 16); + } + for (var18 = 0; var18 < var13; ++var18) + { + var17 -= (int) ((long) samples[var14 - 1 - var18] + * (long) SoundEffectDefinition.coefficients[1][var18] >> 16); + } + InstrumentDefinition.samples[var14] = var17; + this.filterEnvelope.step(var1 + 1); + ++var14; + } + break; + } + var12 = this.filter.compute(0, (float) var11 / 65536.0f); + var13 = this.filter.compute(1, (float) var11 / 65536.0f); + var15 += 128; + } + while (true); + } + } + for (var11 = 0; var11 < var1; ++var11) + { + if (samples[var11] < -32768) + { + InstrumentDefinition.samples[var11] = -32768; + } + + if (samples[var11] <= 32767) + { + continue; + } + + InstrumentDefinition.samples[var11] = 32767; + } + + return samples; + } + + private static void method3854(int[] var1, int var2, int var3) + { + var3 = var3 + var2 - 7; + + while (var2 < var3) + { + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + var1[var2++] = 0; + } + + while (var2 < (var3 += 7)) + { + var1[var2++] = 0; + } + } + + public final int evaluateWave(int var1, int var2, int var3) + { + return var3 == 1 ? ((var1 & 32767) < 16384 ? var2 : -var2) : (var3 == 2 ? AUDIO_SINE[var1 & 32767] * var2 >> 14 : (var3 == 3 ? (var2 * (var1 & 32767) >> 14) - var2 : (var3 == 4 ? var2 * NOISE[var1 / 2607 & 32767] : 0))); + } +} + diff --git a/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectDefinition.java index 0eda10254e..7f6d71dc17 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectDefinition.java +++ b/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectDefinition.java @@ -26,7 +26,102 @@ package net.runelite.cache.definitions.sound; public class SoundEffectDefinition { - public int field1006; - public SoundEffect1Definition[] field1008 = new SoundEffect1Definition[10]; - public int field1009; -} + public int[][][] phases = new int[2][2][4]; + public int[] pairs = new int[2]; + public int[] unity = new int[2]; + public int[][][] magnitudes = new int[2][2][4]; + + public static float[][] minCoefficients = new float[2][8]; + public static int[][] coefficients = new int[2][8]; + public static float fowardMinCoefficientMultiplier; + public static int fowardMultiplier; + + public int compute(int var1, float var2) + { + float var3; + int var4; + + if (var1 == 0) + { + var3 = (float) this.unity[0] + (float) (this.unity[1] - this.unity[0]) * var2; + + fowardMinCoefficientMultiplier = (float) Math.pow(0.1, (var3 *= 0.0030517578f) / 20.0f); + fowardMultiplier = (int) (fowardMinCoefficientMultiplier * 65536.0f); + } + + if (this.pairs[var1] == 0) + { + return 0; + } + + var3 = this.interpolateMagniture(var1, 0, var2); + + minCoefficients[var1][0] = -2.0f * var3 * (float) Math.cos(this.interpolatePhase(var1, 0, var2)); + minCoefficients[var1][1] = var3 * var3; + + for (var4 = 1; var4 < this.pairs[var1]; ++var4) + { + var3 = this.interpolateMagniture(var1, var4, var2); + + float var5 = -2.0f * var3 * (float) Math.cos(this.interpolatePhase(var1, var4, var2)); + float var6 = var3 * var3; + + minCoefficients[var1][var4 * 2 + 1] = minCoefficients[var1][var4 * 2 - 1] * var6; + minCoefficients[var1][var4 * 2] = minCoefficients[var1][var4 * 2 - 1] * var5 + minCoefficients[var1][var4 * 2 - 2] * var6; + + for (int var7 = var4 * 2 - 1; var7 >= 2; --var7) + { + float[] arrf = minCoefficients[var1]; + int n = var7; + + arrf[n] = arrf[n] + (minCoefficients[var1][var7 - 1] * var5 + minCoefficients[var1][var7 - 2] * var6); + } + + float[] arrf = minCoefficients[var1]; + arrf[1] = arrf[1] + (minCoefficients[var1][0] * var5 + var6); + + float[] arrf2 = minCoefficients[var1]; + arrf2[0] = arrf2[0] + var5; + } + + if (var1 == 0) + { + var4 = 0; + while (var4 < this.pairs[0] * 2) + { + float[] arrf = minCoefficients[0]; + int n = var4++; + + arrf[n] = arrf[n] * fowardMinCoefficientMultiplier; + } + } + + for (var4 = 0; var4 < this.pairs[var1] * 2; ++var4) + { + coefficients[var1][var4] = (int) (minCoefficients[var1][var4] * 65536.0f); + } + + return this.pairs[var1] * 2; + } + + public float interpolateMagniture(int var1, int var2, float var3) + { + float var4 = (float) this.magnitudes[var1][0][var2] + var3 * (float) (this.magnitudes[var1][1][var2] - this.magnitudes[var1][0][var2]); + + return 1.0f - (float) Math.pow(10.0, (-(var4 *= 0.0015258789f)) / 20.0f); + } + + public float interpolatePhase(int var1, int var2, float var3) + { + float var4 = (float) this.phases[var1][0][var2] + var3 * (float) (this.phases[var1][1][var2] - this.phases[var1][0][var2]); + + return normalise(var4 *= 1.2207031E-4f); + } + + public static float normalise(float var1) + { + float var2 = 32.703197f * (float) Math.pow(2.0, var1); + + return var2 * 3.1415927f / 11025.0f; + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectTrackDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectTrackDefinition.java new file mode 100644 index 0000000000..6cbbbcc2cf --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/definitions/sound/SoundEffectTrackDefinition.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.sound; + +public class SoundEffectTrackDefinition +{ + public int start; + public InstrumentDefinition[] instruments = new InstrumentDefinition[10]; + public int end; + + public final byte[] mix() + { + int var2; + int var1 = 0; + + for (var2 = 0; var2 < 10; ++var2) + { + if (this.instruments[var2] == null || this.instruments[var2].duration + this.instruments[var2].offset <= var1) + { + continue; + } + + var1 = this.instruments[var2].duration + this.instruments[var2].offset; + } + + if (var1 == 0) + { + return new byte[0]; + } + + var2 = var1 * 22050 / 1000; + + byte[] var3 = new byte[var2]; + + for (int i = 0; i < 10; ++i) + { + if (this.instruments[i] == null) + { + continue; + } + + int var5 = this.instruments[i].duration * 22050 / 1000; + int var6 = this.instruments[i].offset * 22050 / 1000; + + int[] var7 = this.instruments[i].synthesize(var5, this.instruments[i].duration); + + for (int j = 0; j < var5; ++j) + { + int var9 = (var7[j] >> 8) + var3[j + var6]; + + if ((var9 + 128 & -256) != 0) + { + var9 = var9 >> 31 ^ 127; + } + + var3[j + var6] = (byte) var9; + } + } + + return var3; + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/fs/jagex/DiskStorage.java b/cache/src/main/java/net/runelite/cache/fs/jagex/DiskStorage.java index bebcc9711f..42fd8d9b0c 100644 --- a/cache/src/main/java/net/runelite/cache/fs/jagex/DiskStorage.java +++ b/cache/src/main/java/net/runelite/cache/fs/jagex/DiskStorage.java @@ -112,8 +112,13 @@ public class DiskStorage implements Storage public byte[] readIndex(int indexId) throws IOException { IndexEntry entry = index255.read(indexId); - byte[] indexData = data.read(index255.getIndexFileId(), entry.getId(), entry.getSector(), entry.getLength()); - return indexData; + if (entry != null) + { + byte[] indexData = data.read(index255.getIndexFileId(), entry.getId(), entry.getSector(), entry.getLength()); + return indexData; + } + + return null; } private void loadIndex(Index index) throws IOException @@ -121,6 +126,12 @@ public class DiskStorage implements Storage logger.trace("Loading index {}", index.getId()); byte[] indexData = readIndex(index.getId()); + + if (indexData == null) + { + return; + } + Container res = Container.decompress(indexData, null); byte[] data = res.data; diff --git a/cache/src/main/java/net/runelite/cache/io/OutputStream.java b/cache/src/main/java/net/runelite/cache/io/OutputStream.java index 751905f4fb..537c6bb0ba 100644 --- a/cache/src/main/java/net/runelite/cache/io/OutputStream.java +++ b/cache/src/main/java/net/runelite/cache/io/OutputStream.java @@ -26,8 +26,10 @@ package net.runelite.cache.io; import com.google.common.base.Preconditions; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; public final class OutputStream extends java.io.OutputStream { @@ -181,22 +183,23 @@ public final class OutputStream extends java.io.OutputStream public void writeString(String str) { - byte[] b; - try - { - b = str.getBytes("ISO-8859-1"); - } - catch (UnsupportedEncodingException ex) - { - throw new RuntimeException(ex); - } - writeBytes(b); + Charset utf8charset = Charset.forName("UTF-8"); + Charset cp1252charset = Charset.forName("Cp1252"); + + ByteBuffer inputBuffer = ByteBuffer.wrap(str.getBytes()); + + CharBuffer data = utf8charset.decode(inputBuffer); + + ByteBuffer outputBuffer = cp1252charset.encode(data); + byte[] outputData = outputBuffer.array(); + + writeBytes(outputData); writeByte(0); } public byte[] flip() { - buffer.flip(); + ((Buffer) buffer).flip(); byte[] b = new byte[buffer.limit()]; buffer.get(b); return b; @@ -208,4 +211,4 @@ public final class OutputStream extends java.io.OutputStream buffer.put((byte) b); } -} +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/item/ItemSpriteFactory.java b/cache/src/main/java/net/runelite/cache/item/ItemSpriteFactory.java index c93edf3611..dc8922535e 100644 --- a/cache/src/main/java/net/runelite/cache/item/ItemSpriteFactory.java +++ b/cache/src/main/java/net/runelite/cache/item/ItemSpriteFactory.java @@ -38,19 +38,19 @@ import net.runelite.cache.models.VertexNormal; public class ItemSpriteFactory { public static final BufferedImage createSprite(ItemProvider itemProvider, ModelProvider modelProvider, - SpriteProvider spriteProvider, TextureProvider textureProvider, - int itemId, int quantity, int border, int shadowColor, - boolean noted) throws IOException + SpriteProvider spriteProvider, TextureProvider textureProvider, + int itemId, int quantity, int border, int shadowColor, + boolean noted) throws IOException { - SpritePixels spritePixels = createSpritePixels(itemProvider, modelProvider, spriteProvider, textureProvider, - itemId, quantity, border, shadowColor, noted); + Sprite spritePixels = createSpritePixels(itemProvider, modelProvider, spriteProvider, textureProvider, + itemId, quantity, border, shadowColor, noted); return spritePixels == null ? null : spritePixels.toBufferedImage(); } - private static final SpritePixels createSpritePixels(ItemProvider itemProvider, ModelProvider modelProvider, - SpriteProvider spriteProvider, TextureProvider textureProvider, - int itemId, int quantity, int border, int shadowColor, - boolean noted) throws IOException + private static final Sprite createSpritePixels(ItemProvider itemProvider, ModelProvider modelProvider, + SpriteProvider spriteProvider, TextureProvider textureProvider, + int itemId, int quantity, int border, int shadowColor, + boolean noted) throws IOException { ItemDefinition item = itemProvider.provide(itemId); @@ -83,11 +83,11 @@ public class ItemSpriteFactory return null; } - SpritePixels auxSpritePixels = null; + Sprite auxSpritePixels = null; if (item.notedTemplate != -1) { auxSpritePixels = createSpritePixels(itemProvider, modelProvider, spriteProvider, textureProvider, - item.notedID, 10, 1, 0, true); + item.notedID, 10, 1, 0, true); if (auxSpritePixels == null) { return null; @@ -96,7 +96,7 @@ public class ItemSpriteFactory else if (item.boughtTemplateId != -1) { auxSpritePixels = createSpritePixels(itemProvider, modelProvider, spriteProvider, textureProvider, - item.boughtId, quantity, border, shadowColor, false); + item.boughtId, quantity, border, shadowColor, false); if (auxSpritePixels == null) { return null; @@ -105,7 +105,7 @@ public class ItemSpriteFactory else if (item.placeholderTemplateId != -1) { auxSpritePixels = createSpritePixels(itemProvider, modelProvider, spriteProvider, textureProvider, - item.placeholderId, quantity, 0, 0, false); + item.placeholderId, quantity, 0, 0, false); if (auxSpritePixels == null) { return null; @@ -114,7 +114,7 @@ public class ItemSpriteFactory RSTextureProvider rsTextureProvider = new RSTextureProvider(textureProvider, spriteProvider); - SpritePixels spritePixels = new SpritePixels(36, 32); + Sprite spritePixels = new Sprite(36, 32); Graphics3D graphics = new Graphics3D(rsTextureProvider); graphics.setBrightness(0.6d); graphics.setRasterBuffer(spritePixels.pixels, 36, 32); @@ -142,12 +142,12 @@ public class ItemSpriteFactory itemModel.calculateBoundsCylinder(); itemModel.rotateAndProject(graphics, 0, - item.yan2d, - item.zan2d, - item.xan2d, - item.xOffset2d, - itemModel.modelHeight / 2 + var17 + item.yOffset2d, - var18 + item.yOffset2d); + item.yan2d, + item.zan2d, + item.xan2d, + item.xOffset2d, + itemModel.modelHeight / 2 + var17 + item.yOffset2d, + var18 + item.yOffset2d); if (item.boughtTemplateId != -1) { auxSpritePixels.drawAtOn(graphics, 0, 0); @@ -175,8 +175,8 @@ public class ItemSpriteFactory } graphics.setRasterBuffer(graphics.graphicsPixels, - graphics.graphicsPixelsWidth, - graphics.graphicsPixelsHeight); + graphics.graphicsPixelsWidth, + graphics.graphicsPixelsHeight); graphics.setRasterClipping(); graphics.rasterGouraudLowRes = true; diff --git a/cache/src/main/java/net/runelite/cache/item/SpritePixels.java b/cache/src/main/java/net/runelite/cache/item/SpritePixels.java index 8bfada5bc2..ed8be85a18 100644 --- a/cache/src/main/java/net/runelite/cache/item/SpritePixels.java +++ b/cache/src/main/java/net/runelite/cache/item/SpritePixels.java @@ -26,7 +26,7 @@ package net.runelite.cache.item; import java.awt.image.BufferedImage; -class SpritePixels +class Sprite { public int[] pixels; public int width; @@ -34,7 +34,7 @@ class SpritePixels public int offsetX; int offsetY; - public SpritePixels(int[] var1, int var2, int var3) + public Sprite(int[] var1, int var2, int var3) { this.pixels = var1; this.width = var2; @@ -43,7 +43,7 @@ class SpritePixels this.offsetX = 0; } - public SpritePixels(int var1, int var2) + public Sprite(int var1, int var2) { this(new int[var2 * var1], var1, var2); } @@ -108,7 +108,7 @@ class SpritePixels } - public void drawAtOn(Rasterizer2D graphics, int x, int y) + public void drawAtOn(Rasterizer2D graphics, int x, int y) { x += this.offsetX; y += this.offsetY; diff --git a/cache/src/main/java/net/runelite/cache/script/Instructions.java b/cache/src/main/java/net/runelite/cache/script/Instructions.java index 17dfabfcd1..6c611d7df1 100644 --- a/cache/src/main/java/net/runelite/cache/script/Instructions.java +++ b/cache/src/main/java/net/runelite/cache/script/Instructions.java @@ -78,6 +78,7 @@ public class Instructions add(CC_SETSIZE, "cc_setsize"); add(CC_SETHIDE, "cc_sethide"); add(CC_SETNOCLICKTHROUGH, "cc_setnoclickthrough"); + add(CC_SETNOSCROLLTHROUGH, "cc_setnoscrollthrough"); add(CC_SETSCROLLPOS, "cc_setscrollpos"); add(CC_SETCOLOUR, "cc_setcolour"); add(CC_SETFILL, "cc_setfill"); @@ -102,6 +103,7 @@ public class Instructions add(CC_RESUME_PAUSEBUTTON, "cc_resume_pausebutton"); add(CC_SETFILLCOLOUR, "cc_setfillcolour"); add(CC_SETLINEDIRECTION, "cc_setlinedirection"); + add(CC_SETMODELTRANSPARENT, "cc_setmodeltransparent"); add(CC_SETOBJECT, "cc_setobject"); add(CC_SETNPCHEAD, "cc_setnpchead"); add(CC_SETPLAYERHEAD_SELF, "cc_setplayerhead_self"); @@ -115,6 +117,12 @@ public class Instructions add(CC_SETOPBASE, "cc_setopbase"); add(CC_SETTARGETVERB, "cc_settargetverb"); add(CC_CLEAROPS, "cc_clearops"); + add(CC_SETOPKEY, "cc_setopkey"); + add(CC_SETOPTKEY, "cc_setoptkey"); + add(CC_SETOPKEYRATE, "cc_setopkeyrate"); + add(CC_SETOPTKEYRATE, "cc_setoptkeyrate"); + add(CC_SETOPKEYIGNOREHELD, "cc_setopkeyignoreheld"); + add(CC_SETOPTKEYIGNOREHELD, "cc_setoptkeyignoreheld"); add(CC_SETONCLICK, "cc_setonclick"); add(CC_SETONHOLD, "cc_setonhold"); add(CC_SETONRELEASE, "cc_setonrelease"); @@ -159,6 +167,7 @@ public class Instructions add(CC_GETTRANS, "cc_gettrans"); add(CC_GETCOLOUR, "cc_getcolour"); add(CC_GETFILLCOLOUR, "cc_getfillcolour"); + add(CC_GETMODELTRANSPARENT, "cc_getmodeltransparent"); add(CC_GETINVOBJECT, "cc_getinvobject"); add(CC_GETINVCOUNT, "cc_getinvcount"); add(CC_GETID, "cc_getid"); @@ -194,6 +203,7 @@ public class Instructions add(IF_RESUME_PAUSEBUTTON, "if_resume_pausebutton"); add(IF_SETFILLCOLOUR, "if_setfillcolour"); add(IF_SETLINEDIRECTION, "if_setlinedirection"); + add(IF_SETMODELTRANSPARENT, "if_setmodeltransparent"); add(IF_SETOBJECT, "if_setobject"); add(IF_SETNPCHEAD, "if_setnpchead"); add(IF_SETPLAYERHEAD_SELF, "if_setplayerhead_self"); @@ -257,6 +267,7 @@ public class Instructions add(IF_GETTRANS, "if_gettrans"); add(IF_GETCOLOUR, "if_getcolour"); add(IF_GETFILLCOLOUR, "if_getfillcolour"); + add(IF_GETMODELTRANSPARENT, "if_getmodeltransparent"); add(IF_GETINVOBJECT, "if_getinvobject"); add(IF_GETINVCOUNT, "if_getinvcount"); add(IF_HASSUB, "if_hassub"); @@ -287,11 +298,17 @@ public class Instructions add(SETSHOWLOADINGMESSAGES, "setshowloadingmessages"); add(SETTAPTODROP, "settaptodrop"); add(GETTAPTODROP, "gettaptodrop"); + add(SETOCULUSORBSPEED, "setoculusorbspeed"); add(GETCANVASSIZE, "getcanvassize"); + add(MOBILE_SETFPS, "mobile_setfps"); + add(MOBILE_OPENSTORE, "mobile_openstore"); + add(MOBILE_OPENSTORECATEGORY, "mobile_openstorecategory"); add(SETHIDEUSERNAME, "sethideusername"); add(GETHIDEUSERNAME, "gethideusername"); add(SETREMEMBERUSERNAME, "setrememberusername"); add(GETREMEMBERUSERNAME, "getrememberusername"); + add(SETTITLEMUSICENABLED, "settitlemusicenabled"); + add(GETTITLEMUSICENABLED, "gettitlemusicenabled"); add(SOUND_SYNTH, "sound_synth"); add(SOUND_SONG, "sound_song"); add(SOUND_JINGLE, "sound_jingle"); @@ -411,6 +428,7 @@ public class Instructions add(REMOVETAGS, "removetags"); add(STRING_INDEXOF_CHAR, "string_indexof_char"); add(STRING_INDEXOF_STRING, "string_indexof_string"); + add(UPPERCASE, "uppercase"); add(OC_NAME, "oc_name"); add(OC_OP, "oc_op"); add(OC_IOP, "oc_iop"); @@ -461,6 +479,7 @@ public class Instructions add(WORLDLIST_NEXT, "worldlist_next"); add(WORLDLIST_SPECIFIC, "worldlist_specific"); add(WORLDLIST_SORT, "worldlist_sort"); + add(GETWORLDINFO, "getworldinfo"); add(SETFOLLOWEROPSLOWPRIORITY, "setfolloweropslowpriority"); add(NC_PARAM, "nc_param"); add(LC_PARAM, "lc_param"); @@ -468,6 +487,7 @@ public class Instructions add(STRUCT_PARAM, "struct_param"); add(ON_MOBILE, "on_mobile"); add(CLIENTTYPE, "clienttype"); + add(MOBILE_KEYBOARDHIDE, "mobile_keyboardhide"); add(BATTERYLEVEL, "batterylevel"); add(BATTERYCHARGING, "batterycharging"); add(WIFIAVAILABLE, "wifiavailable"); @@ -505,6 +525,8 @@ public class Instructions add(MEC_TEXTSIZE, "mec_textsize"); add(MEC_CATEGORY, "mec_category"); add(MEC_SPRITE, "mec_sprite"); + add(WORLDMAP_ELEMENT, "worldmap_element"); + add(WORLDMAP_ELEMENTCOORD, "worldmap_elementcoord"); } protected void add(int opcode, String name) diff --git a/cache/src/main/java/net/runelite/cache/script/Opcodes.java b/cache/src/main/java/net/runelite/cache/script/Opcodes.java index e69a1ef5ef..1485b9982e 100644 --- a/cache/src/main/java/net/runelite/cache/script/Opcodes.java +++ b/cache/src/main/java/net/runelite/cache/script/Opcodes.java @@ -69,6 +69,7 @@ public class Opcodes public static final int CC_SETSIZE = 1001; public static final int CC_SETHIDE = 1003; public static final int CC_SETNOCLICKTHROUGH = 1005; + public static final int CC_SETNOSCROLLTHROUGH = 1006; public static final int CC_SETSCROLLPOS = 1100; public static final int CC_SETCOLOUR = 1101; public static final int CC_SETFILL = 1102; @@ -93,6 +94,7 @@ public class Opcodes public static final int CC_RESUME_PAUSEBUTTON = 1121; public static final int CC_SETFILLCOLOUR = 1123; public static final int CC_SETLINEDIRECTION = 1126; + public static final int CC_SETMODELTRANSPARENT = 1127; public static final int CC_SETOBJECT = 1200; public static final int CC_SETNPCHEAD = 1201; public static final int CC_SETPLAYERHEAD_SELF = 1202; @@ -106,6 +108,12 @@ public class Opcodes public static final int CC_SETOPBASE = 1305; public static final int CC_SETTARGETVERB = 1306; public static final int CC_CLEAROPS = 1307; + public static final int CC_SETOPKEY = 1350; + public static final int CC_SETOPTKEY = 1351; + public static final int CC_SETOPKEYRATE = 1352; + public static final int CC_SETOPTKEYRATE = 1353; + public static final int CC_SETOPKEYIGNOREHELD = 1354; + public static final int CC_SETOPTKEYIGNOREHELD = 1355; public static final int CC_SETONCLICK = 1400; public static final int CC_SETONHOLD = 1401; public static final int CC_SETONRELEASE = 1402; @@ -150,6 +158,7 @@ public class Opcodes public static final int CC_GETTRANS = 1609; public static final int CC_GETCOLOUR = 1611; public static final int CC_GETFILLCOLOUR = 1612; + public static final int CC_GETMODELTRANSPARENT = 1614; public static final int CC_GETINVOBJECT = 1700; public static final int CC_GETINVCOUNT = 1701; public static final int CC_GETID = 1702; @@ -185,6 +194,7 @@ public class Opcodes public static final int IF_RESUME_PAUSEBUTTON = 2121; public static final int IF_SETFILLCOLOUR = 2123; public static final int IF_SETLINEDIRECTION = 2126; + public static final int IF_SETMODELTRANSPARENT = 2127; public static final int IF_SETOBJECT = 2200; public static final int IF_SETNPCHEAD = 2201; public static final int IF_SETPLAYERHEAD_SELF = 2202; @@ -248,6 +258,7 @@ public class Opcodes public static final int IF_GETTRANS = 2609; public static final int IF_GETCOLOUR = 2611; public static final int IF_GETFILLCOLOUR = 2612; + public static final int IF_GETMODELTRANSPARENT = 2614; public static final int IF_GETINVOBJECT = 2700; public static final int IF_GETINVCOUNT = 2701; public static final int IF_HASSUB = 2702; @@ -278,11 +289,17 @@ public class Opcodes public static final int SETSHOWLOADINGMESSAGES = 3126; public static final int SETTAPTODROP = 3127; public static final int GETTAPTODROP = 3128; + public static final int SETOCULUSORBSPEED = 3129; public static final int GETCANVASSIZE = 3132; + public static final int MOBILE_SETFPS = 3133; + public static final int MOBILE_OPENSTORE = 3134; + public static final int MOBILE_OPENSTORECATEGORY = 3135; public static final int SETHIDEUSERNAME = 3141; public static final int GETHIDEUSERNAME = 3142; public static final int SETREMEMBERUSERNAME = 3143; public static final int GETREMEMBERUSERNAME = 3144; + public static final int SETTITLEMUSICENABLED = 3146; + public static final int GETTITLEMUSICENABLED = 3147; public static final int SOUND_SYNTH = 3200; public static final int SOUND_SONG = 3201; public static final int SOUND_JINGLE = 3202; @@ -402,6 +419,7 @@ public class Opcodes public static final int REMOVETAGS = 4119; public static final int STRING_INDEXOF_CHAR = 4120; public static final int STRING_INDEXOF_STRING = 4121; + public static final int UPPERCASE = 4122; public static final int OC_NAME = 4200; public static final int OC_OP = 4201; public static final int OC_IOP = 4202; @@ -452,6 +470,7 @@ public class Opcodes public static final int WORLDLIST_NEXT = 6502; public static final int WORLDLIST_SPECIFIC = 6506; public static final int WORLDLIST_SORT = 6507; + public static final int GETWORLDINFO = 6511; public static final int SETFOLLOWEROPSLOWPRIORITY = 6512; public static final int NC_PARAM = 6513; public static final int LC_PARAM = 6514; @@ -459,6 +478,7 @@ public class Opcodes public static final int STRUCT_PARAM = 6516; public static final int ON_MOBILE = 6518; public static final int CLIENTTYPE = 6519; + public static final int MOBILE_KEYBOARDHIDE = 6521; public static final int BATTERYLEVEL = 6524; public static final int BATTERYCHARGING = 6525; public static final int WIFIAVAILABLE = 6526; @@ -496,4 +516,6 @@ public class Opcodes public static final int MEC_TEXTSIZE = 6694; public static final int MEC_CATEGORY = 6695; public static final int MEC_SPRITE = 6696; + public static final int WORLDMAP_ELEMENT = 6697; + public static final int WORLDMAP_ELEMENTCOORD = 6699; } diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java b/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java index 643057911e..85c37c4048 100644 --- a/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java +++ b/cache/src/main/java/net/runelite/cache/script/assembler/Assembler.java @@ -29,7 +29,7 @@ import java.io.InputStream; import net.runelite.cache.definitions.ScriptDefinition; import net.runelite.cache.script.Instructions; import net.runelite.cache.script.assembler.rs2asmParser.ProgContext; -import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTreeWalker; @@ -45,7 +45,7 @@ public class Assembler public ScriptDefinition assemble(InputStream in) throws IOException { // Get our lexer - rs2asmLexer lexer = new rs2asmLexer(new ANTLRInputStream(in)); + rs2asmLexer lexer = new rs2asmLexer(CharStreams.fromStream(in)); LexerErrorListener errorListener = new LexerErrorListener(); lexer.addErrorListener(errorListener); diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java index 2797d4a4c6..60ebfb3369 100644 --- a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java +++ b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java @@ -259,7 +259,7 @@ public class ScriptWriter extends rs2asmBaseListener } int index = 0; - Map[] maps = new Map[count]; + @SuppressWarnings("unchecked") Map[] maps = new Map[count]; for (LookupSwitch lswitch : switches) { if (lswitch == null) diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmBaseListener.java b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmBaseListener.java new file mode 100644 index 0000000000..b793e66c65 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmBaseListener.java @@ -0,0 +1,554 @@ +// Generated from net\runelite\cache\script\assembler\rs2asm.g4 by ANTLR 4.6 +package net.runelite.cache.script.assembler; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * This class provides an empty implementation of {@link rs2asmListener}, + * which can be extended to create a listener which only needs to handle a subset + * of the available methods. + */ +public class rs2asmBaseListener implements rs2asmListener +{ + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterProg(rs2asmParser.ProgContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitProg(rs2asmParser.ProgContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterHeader(rs2asmParser.HeaderContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitHeader(rs2asmParser.HeaderContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterId(rs2asmParser.IdContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitId(rs2asmParser.IdContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInt_stack_count(rs2asmParser.Int_stack_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInt_stack_count(rs2asmParser.Int_stack_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterString_stack_count(rs2asmParser.String_stack_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitString_stack_count(rs2asmParser.String_stack_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInt_var_count(rs2asmParser.Int_var_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInt_var_count(rs2asmParser.Int_var_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterString_var_count(rs2asmParser.String_var_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitString_var_count(rs2asmParser.String_var_countContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterId_value(rs2asmParser.Id_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitId_value(rs2asmParser.Id_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInt_stack_value(rs2asmParser.Int_stack_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInt_stack_value(rs2asmParser.Int_stack_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterString_stack_value(rs2asmParser.String_stack_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitString_stack_value(rs2asmParser.String_stack_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInt_var_value(rs2asmParser.Int_var_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInt_var_value(rs2asmParser.Int_var_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterString_var_value(rs2asmParser.String_var_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitString_var_value(rs2asmParser.String_var_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterLine(rs2asmParser.LineContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitLine(rs2asmParser.LineContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInstruction(rs2asmParser.InstructionContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInstruction(rs2asmParser.InstructionContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterLabel(rs2asmParser.LabelContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitLabel(rs2asmParser.LabelContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInstruction_name(rs2asmParser.Instruction_nameContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInstruction_name(rs2asmParser.Instruction_nameContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterName_string(rs2asmParser.Name_stringContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitName_string(rs2asmParser.Name_stringContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterName_opcode(rs2asmParser.Name_opcodeContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitName_opcode(rs2asmParser.Name_opcodeContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterInstruction_operand(rs2asmParser.Instruction_operandContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitInstruction_operand(rs2asmParser.Instruction_operandContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterOperand_int(rs2asmParser.Operand_intContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitOperand_int(rs2asmParser.Operand_intContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterOperand_qstring(rs2asmParser.Operand_qstringContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitOperand_qstring(rs2asmParser.Operand_qstringContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterOperand_label(rs2asmParser.Operand_labelContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitOperand_label(rs2asmParser.Operand_labelContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterSwitch_lookup(rs2asmParser.Switch_lookupContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitSwitch_lookup(rs2asmParser.Switch_lookupContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterSwitch_key(rs2asmParser.Switch_keyContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitSwitch_key(rs2asmParser.Switch_keyContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterSwitch_value(rs2asmParser.Switch_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitSwitch_value(rs2asmParser.Switch_valueContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void visitTerminal(TerminalNode node) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void visitErrorNode(ErrorNode node) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void enterEveryRule(ParserRuleContext ctx) + { + } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override + public void exitEveryRule(ParserRuleContext ctx) + { + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmLexer.java b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmLexer.java new file mode 100644 index 0000000000..15055397fd --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmLexer.java @@ -0,0 +1,171 @@ +// Generated from net\runelite\cache\script\assembler\rs2asm.g4 by ANTLR 4.6 +package net.runelite.cache.script.assembler; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class rs2asmLexer extends Lexer +{ + public static final int + T__0 = 1, T__1 = 2, T__2 = 3, T__3 = 4, T__4 = 5, T__5 = 6, NEWLINE = 7, INT = 8, QSTRING = 9, + IDENTIFIER = 10, COMMENT = 11, WS = 12; + public static final String[] ruleNames = { + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "NEWLINE", "INT", "QSTRING", + "IDENTIFIER", "COMMENT", "WS" + }; + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + public static final String _serializedATN = + "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2\16\u0099\b\1\4\2" + + "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4" + + "\13\t\13\4\f\t\f\4\r\t\r\3\2\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\3\3" + + "\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3\4\3\4\3\4\3\4" + + "\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3" + + "\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3\6" + + "\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\7\3\7\3" + + "\b\6\bn\n\b\r\b\16\bo\3\t\5\ts\n\t\3\t\6\tv\n\t\r\t\16\tw\3\n\3\n\3\n" + + "\3\n\7\n~\n\n\f\n\16\n\u0081\13\n\3\n\3\n\3\13\6\13\u0086\n\13\r\13\16" + + "\13\u0087\3\f\3\f\7\f\u008c\n\f\f\f\16\f\u008f\13\f\3\f\3\f\3\r\6\r\u0094" + + "\n\r\r\r\16\r\u0095\3\r\3\r\2\2\16\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n" + + "\23\13\25\f\27\r\31\16\3\2\b\4\2\f\f\17\17\3\2\62;\6\2\f\f\17\17$$^^\4" + + "\2$$^^\6\2\62;C\\aac|\4\2\13\13\"\"\u00a0\2\3\3\2\2\2\2\5\3\2\2\2\2\7" + + "\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2" + + "\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\3\33\3\2\2\2\5" + + " \3\2\2\2\7\62\3\2\2\2\tG\3\2\2\2\13W\3\2\2\2\rj\3\2\2\2\17m\3\2\2\2\21" + + "r\3\2\2\2\23y\3\2\2\2\25\u0085\3\2\2\2\27\u0089\3\2\2\2\31\u0093\3\2\2" + + "\2\33\34\7\60\2\2\34\35\7k\2\2\35\36\7f\2\2\36\37\7\"\2\2\37\4\3\2\2\2" + + " !\7\60\2\2!\"\7k\2\2\"#\7p\2\2#$\7v\2\2$%\7a\2\2%&\7u\2\2&\'\7v\2\2\'" + + "(\7c\2\2()\7e\2\2)*\7m\2\2*+\7a\2\2+,\7e\2\2,-\7q\2\2-.\7w\2\2./\7p\2" + + "\2/\60\7v\2\2\60\61\7\"\2\2\61\6\3\2\2\2\62\63\7\60\2\2\63\64\7u\2\2\64" + + "\65\7v\2\2\65\66\7t\2\2\66\67\7k\2\2\678\7p\2\289\7i\2\29:\7a\2\2:;\7" + + "u\2\2;<\7v\2\2<=\7c\2\2=>\7e\2\2>?\7m\2\2?@\7a\2\2@A\7e\2\2AB\7q\2\2B" + + "C\7w\2\2CD\7p\2\2DE\7v\2\2EF\7\"\2\2F\b\3\2\2\2GH\7\60\2\2HI\7k\2\2IJ" + + "\7p\2\2JK\7v\2\2KL\7a\2\2LM\7x\2\2MN\7c\2\2NO\7t\2\2OP\7a\2\2PQ\7e\2\2" + + "QR\7q\2\2RS\7w\2\2ST\7p\2\2TU\7v\2\2UV\7\"\2\2V\n\3\2\2\2WX\7\60\2\2X" + + "Y\7u\2\2YZ\7v\2\2Z[\7t\2\2[\\\7k\2\2\\]\7p\2\2]^\7i\2\2^_\7a\2\2_`\7x" + + "\2\2`a\7c\2\2ab\7t\2\2bc\7a\2\2cd\7e\2\2de\7q\2\2ef\7w\2\2fg\7p\2\2gh" + + "\7v\2\2hi\7\"\2\2i\f\3\2\2\2jk\7<\2\2k\16\3\2\2\2ln\t\2\2\2ml\3\2\2\2" + + "no\3\2\2\2om\3\2\2\2op\3\2\2\2p\20\3\2\2\2qs\7/\2\2rq\3\2\2\2rs\3\2\2" + + "\2su\3\2\2\2tv\t\3\2\2ut\3\2\2\2vw\3\2\2\2wu\3\2\2\2wx\3\2\2\2x\22\3\2" + + "\2\2y\177\7$\2\2z~\n\4\2\2{|\7^\2\2|~\t\5\2\2}z\3\2\2\2}{\3\2\2\2~\u0081" + + "\3\2\2\2\177}\3\2\2\2\177\u0080\3\2\2\2\u0080\u0082\3\2\2\2\u0081\177" + + "\3\2\2\2\u0082\u0083\7$\2\2\u0083\24\3\2\2\2\u0084\u0086\t\6\2\2\u0085" + + "\u0084\3\2\2\2\u0086\u0087\3\2\2\2\u0087\u0085\3\2\2\2\u0087\u0088\3\2" + + "\2\2\u0088\26\3\2\2\2\u0089\u008d\7=\2\2\u008a\u008c\n\2\2\2\u008b\u008a" + + "\3\2\2\2\u008c\u008f\3\2\2\2\u008d\u008b\3\2\2\2\u008d\u008e\3\2\2\2\u008e" + + "\u0090\3\2\2\2\u008f\u008d\3\2\2\2\u0090\u0091\b\f\2\2\u0091\30\3\2\2" + + "\2\u0092\u0094\t\7\2\2\u0093\u0092\3\2\2\2\u0094\u0095\3\2\2\2\u0095\u0093" + + "\3\2\2\2\u0095\u0096\3\2\2\2\u0096\u0097\3\2\2\2\u0097\u0098\b\r\2\2\u0098" + + "\32\3\2\2\2\13\2orw}\177\u0087\u008d\u0095\3\2\3\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + private static final String[] _LITERAL_NAMES = { + null, "'.id '", "'.int_stack_count '", "'.string_stack_count '", "'.int_var_count '", + "'.string_var_count '", "':'" + }; + private static final String[] _SYMBOLIC_NAMES = { + null, null, null, null, null, null, null, "NEWLINE", "INT", "QSTRING", + "IDENTIFIER", "COMMENT", "WS" + }; + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + static + { + RuntimeMetaData.checkVersion("4.6", RuntimeMetaData.VERSION); + } + + static + { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) + { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) + { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) + { + tokenNames[i] = ""; + } + } + } + + static + { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) + { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } + + public rs2asmLexer(CharStream input) + { + super(input); + _interp = new LexerATNSimulator(this, _ATN, _decisionToDFA, _sharedContextCache); + } + + @Override + public String[] getRuleNames() + { + return ruleNames; + } + + @Override + + public Vocabulary getVocabulary() + { + return VOCABULARY; + } + + @Override + public String getSerializedATN() + { + return _serializedATN; + } + + @Override + public String getGrammarFileName() + { + return "rs2asm.g4"; + } + + @Override + public ATN getATN() + { + return _ATN; + } + + @Override + public String[] getModeNames() + { + return modeNames; + } + + @Override + @Deprecated + public String[] getTokenNames() + { + return tokenNames; + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmListener.java b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmListener.java new file mode 100644 index 0000000000..d4d8e39ab0 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmListener.java @@ -0,0 +1,361 @@ +// Generated from net\runelite\cache\script\assembler\rs2asm.g4 by ANTLR 4.6 +package net.runelite.cache.script.assembler; + +import org.antlr.v4.runtime.tree.ParseTreeListener; + +/** + * This interface defines a complete listener for a parse tree produced by + * {@link rs2asmParser}. + */ +public interface rs2asmListener extends ParseTreeListener +{ + /** + * Enter a parse tree produced by {@link rs2asmParser#prog}. + * + * @param ctx the parse tree + */ + void enterProg(rs2asmParser.ProgContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#prog}. + * + * @param ctx the parse tree + */ + void exitProg(rs2asmParser.ProgContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#header}. + * + * @param ctx the parse tree + */ + void enterHeader(rs2asmParser.HeaderContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#header}. + * + * @param ctx the parse tree + */ + void exitHeader(rs2asmParser.HeaderContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#id}. + * + * @param ctx the parse tree + */ + void enterId(rs2asmParser.IdContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#id}. + * + * @param ctx the parse tree + */ + void exitId(rs2asmParser.IdContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#int_stack_count}. + * + * @param ctx the parse tree + */ + void enterInt_stack_count(rs2asmParser.Int_stack_countContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#int_stack_count}. + * + * @param ctx the parse tree + */ + void exitInt_stack_count(rs2asmParser.Int_stack_countContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#string_stack_count}. + * + * @param ctx the parse tree + */ + void enterString_stack_count(rs2asmParser.String_stack_countContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#string_stack_count}. + * + * @param ctx the parse tree + */ + void exitString_stack_count(rs2asmParser.String_stack_countContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#int_var_count}. + * + * @param ctx the parse tree + */ + void enterInt_var_count(rs2asmParser.Int_var_countContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#int_var_count}. + * + * @param ctx the parse tree + */ + void exitInt_var_count(rs2asmParser.Int_var_countContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#string_var_count}. + * + * @param ctx the parse tree + */ + void enterString_var_count(rs2asmParser.String_var_countContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#string_var_count}. + * + * @param ctx the parse tree + */ + void exitString_var_count(rs2asmParser.String_var_countContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#id_value}. + * + * @param ctx the parse tree + */ + void enterId_value(rs2asmParser.Id_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#id_value}. + * + * @param ctx the parse tree + */ + void exitId_value(rs2asmParser.Id_valueContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#int_stack_value}. + * + * @param ctx the parse tree + */ + void enterInt_stack_value(rs2asmParser.Int_stack_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#int_stack_value}. + * + * @param ctx the parse tree + */ + void exitInt_stack_value(rs2asmParser.Int_stack_valueContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#string_stack_value}. + * + * @param ctx the parse tree + */ + void enterString_stack_value(rs2asmParser.String_stack_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#string_stack_value}. + * + * @param ctx the parse tree + */ + void exitString_stack_value(rs2asmParser.String_stack_valueContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#int_var_value}. + * + * @param ctx the parse tree + */ + void enterInt_var_value(rs2asmParser.Int_var_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#int_var_value}. + * + * @param ctx the parse tree + */ + void exitInt_var_value(rs2asmParser.Int_var_valueContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#string_var_value}. + * + * @param ctx the parse tree + */ + void enterString_var_value(rs2asmParser.String_var_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#string_var_value}. + * + * @param ctx the parse tree + */ + void exitString_var_value(rs2asmParser.String_var_valueContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#line}. + * + * @param ctx the parse tree + */ + void enterLine(rs2asmParser.LineContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#line}. + * + * @param ctx the parse tree + */ + void exitLine(rs2asmParser.LineContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#instruction}. + * + * @param ctx the parse tree + */ + void enterInstruction(rs2asmParser.InstructionContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#instruction}. + * + * @param ctx the parse tree + */ + void exitInstruction(rs2asmParser.InstructionContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#label}. + * + * @param ctx the parse tree + */ + void enterLabel(rs2asmParser.LabelContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#label}. + * + * @param ctx the parse tree + */ + void exitLabel(rs2asmParser.LabelContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#instruction_name}. + * + * @param ctx the parse tree + */ + void enterInstruction_name(rs2asmParser.Instruction_nameContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#instruction_name}. + * + * @param ctx the parse tree + */ + void exitInstruction_name(rs2asmParser.Instruction_nameContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#name_string}. + * + * @param ctx the parse tree + */ + void enterName_string(rs2asmParser.Name_stringContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#name_string}. + * + * @param ctx the parse tree + */ + void exitName_string(rs2asmParser.Name_stringContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#name_opcode}. + * + * @param ctx the parse tree + */ + void enterName_opcode(rs2asmParser.Name_opcodeContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#name_opcode}. + * + * @param ctx the parse tree + */ + void exitName_opcode(rs2asmParser.Name_opcodeContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#instruction_operand}. + * + * @param ctx the parse tree + */ + void enterInstruction_operand(rs2asmParser.Instruction_operandContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#instruction_operand}. + * + * @param ctx the parse tree + */ + void exitInstruction_operand(rs2asmParser.Instruction_operandContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#operand_int}. + * + * @param ctx the parse tree + */ + void enterOperand_int(rs2asmParser.Operand_intContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#operand_int}. + * + * @param ctx the parse tree + */ + void exitOperand_int(rs2asmParser.Operand_intContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#operand_qstring}. + * + * @param ctx the parse tree + */ + void enterOperand_qstring(rs2asmParser.Operand_qstringContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#operand_qstring}. + * + * @param ctx the parse tree + */ + void exitOperand_qstring(rs2asmParser.Operand_qstringContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#operand_label}. + * + * @param ctx the parse tree + */ + void enterOperand_label(rs2asmParser.Operand_labelContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#operand_label}. + * + * @param ctx the parse tree + */ + void exitOperand_label(rs2asmParser.Operand_labelContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#switch_lookup}. + * + * @param ctx the parse tree + */ + void enterSwitch_lookup(rs2asmParser.Switch_lookupContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#switch_lookup}. + * + * @param ctx the parse tree + */ + void exitSwitch_lookup(rs2asmParser.Switch_lookupContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#switch_key}. + * + * @param ctx the parse tree + */ + void enterSwitch_key(rs2asmParser.Switch_keyContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#switch_key}. + * + * @param ctx the parse tree + */ + void exitSwitch_key(rs2asmParser.Switch_keyContext ctx); + + /** + * Enter a parse tree produced by {@link rs2asmParser#switch_value}. + * + * @param ctx the parse tree + */ + void enterSwitch_value(rs2asmParser.Switch_valueContext ctx); + + /** + * Exit a parse tree produced by {@link rs2asmParser#switch_value}. + * + * @param ctx the parse tree + */ + void exitSwitch_value(rs2asmParser.Switch_valueContext ctx); +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmParser.java b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmParser.java new file mode 100644 index 0000000000..29058065e0 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/assembler/rs2asmParser.java @@ -0,0 +1,2000 @@ +// Generated from net\runelite\cache\script\assembler\rs2asm.g4 by ANTLR 4.6 +package net.runelite.cache.script.assembler; + +import java.util.List; +import org.antlr.v4.runtime.NoViableAltException; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.RuntimeMetaData; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.Vocabulary; +import org.antlr.v4.runtime.VocabularyImpl; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.TerminalNode; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class rs2asmParser extends Parser +{ + public static final int + T__0 = 1, T__1 = 2, T__2 = 3, T__3 = 4, T__4 = 5, T__5 = 6, NEWLINE = 7, INT = 8, QSTRING = 9, + IDENTIFIER = 10, COMMENT = 11, WS = 12; + public static final int + RULE_prog = 0, RULE_header = 1, RULE_id = 2, RULE_int_stack_count = 3, + RULE_string_stack_count = 4, RULE_int_var_count = 5, RULE_string_var_count = 6, + RULE_id_value = 7, RULE_int_stack_value = 8, RULE_string_stack_value = 9, + RULE_int_var_value = 10, RULE_string_var_value = 11, RULE_line = 12, RULE_instruction = 13, + RULE_label = 14, RULE_instruction_name = 15, RULE_name_string = 16, RULE_name_opcode = 17, + RULE_instruction_operand = 18, RULE_operand_int = 19, RULE_operand_qstring = 20, + RULE_operand_label = 21, RULE_switch_lookup = 22, RULE_switch_key = 23, + RULE_switch_value = 24; + public static final String[] ruleNames = { + "prog", "header", "id", "int_stack_count", "string_stack_count", "int_var_count", + "string_var_count", "id_value", "int_stack_value", "string_stack_value", + "int_var_value", "string_var_value", "line", "instruction", "label", "instruction_name", + "name_string", "name_opcode", "instruction_operand", "operand_int", "operand_qstring", + "operand_label", "switch_lookup", "switch_key", "switch_value" + }; + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + public static final String _serializedATN = + "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3\16\u0097\4\2\t\2" + + "\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13" + + "\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22" + + "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31" + + "\4\32\t\32\3\2\7\2\66\n\2\f\2\16\29\13\2\3\2\3\2\6\2=\n\2\r\2\16\2>\7" + + "\2A\n\2\f\2\16\2D\13\2\3\2\3\2\6\2H\n\2\r\2\16\2I\6\2L\n\2\r\2\16\2M\3" + + "\3\3\3\3\3\3\3\3\3\5\3U\n\3\3\4\3\4\3\4\3\5\3\5\3\5\3\6\3\6\3\6\3\7\3" + + "\7\3\7\3\b\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\r\3\r\3\16\3\16" + + "\3\16\5\16s\n\16\3\17\3\17\3\17\3\20\3\20\3\20\3\21\3\21\5\21}\n\21\3" + + "\22\3\22\3\23\3\23\3\24\3\24\3\24\3\24\5\24\u0087\n\24\3\25\3\25\3\26" + + "\3\26\3\27\3\27\3\30\3\30\3\30\3\30\3\31\3\31\3\32\3\32\3\32\2\2\33\2" + + "\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(*,.\60\62\2\2\u008c\2\67\3" + + "\2\2\2\4T\3\2\2\2\6V\3\2\2\2\bY\3\2\2\2\n\\\3\2\2\2\f_\3\2\2\2\16b\3\2" + + "\2\2\20e\3\2\2\2\22g\3\2\2\2\24i\3\2\2\2\26k\3\2\2\2\30m\3\2\2\2\32r\3" + + "\2\2\2\34t\3\2\2\2\36w\3\2\2\2 |\3\2\2\2\"~\3\2\2\2$\u0080\3\2\2\2&\u0086" + + "\3\2\2\2(\u0088\3\2\2\2*\u008a\3\2\2\2,\u008c\3\2\2\2.\u008e\3\2\2\2\60" + + "\u0092\3\2\2\2\62\u0094\3\2\2\2\64\66\7\t\2\2\65\64\3\2\2\2\669\3\2\2" + + "\2\67\65\3\2\2\2\678\3\2\2\28B\3\2\2\29\67\3\2\2\2:<\5\4\3\2;=\7\t\2\2" + + "<;\3\2\2\2=>\3\2\2\2><\3\2\2\2>?\3\2\2\2?A\3\2\2\2@:\3\2\2\2AD\3\2\2\2" + + "B@\3\2\2\2BC\3\2\2\2CK\3\2\2\2DB\3\2\2\2EG\5\32\16\2FH\7\t\2\2GF\3\2\2" + + "\2HI\3\2\2\2IG\3\2\2\2IJ\3\2\2\2JL\3\2\2\2KE\3\2\2\2LM\3\2\2\2MK\3\2\2" + + "\2MN\3\2\2\2N\3\3\2\2\2OU\5\6\4\2PU\5\b\5\2QU\5\n\6\2RU\5\f\7\2SU\5\16" + + "\b\2TO\3\2\2\2TP\3\2\2\2TQ\3\2\2\2TR\3\2\2\2TS\3\2\2\2U\5\3\2\2\2VW\7" + + "\3\2\2WX\5\20\t\2X\7\3\2\2\2YZ\7\4\2\2Z[\5\22\n\2[\t\3\2\2\2\\]\7\5\2" + + "\2]^\5\24\13\2^\13\3\2\2\2_`\7\6\2\2`a\5\26\f\2a\r\3\2\2\2bc\7\7\2\2c" + + "d\5\30\r\2d\17\3\2\2\2ef\7\n\2\2f\21\3\2\2\2gh\7\n\2\2h\23\3\2\2\2ij\7" + + "\n\2\2j\25\3\2\2\2kl\7\n\2\2l\27\3\2\2\2mn\7\n\2\2n\31\3\2\2\2os\5\34" + + "\17\2ps\5\36\20\2qs\5.\30\2ro\3\2\2\2rp\3\2\2\2rq\3\2\2\2s\33\3\2\2\2" + + "tu\5 \21\2uv\5&\24\2v\35\3\2\2\2wx\7\f\2\2xy\7\b\2\2y\37\3\2\2\2z}\5\"" + + "\22\2{}\5$\23\2|z\3\2\2\2|{\3\2\2\2}!\3\2\2\2~\177\7\f\2\2\177#\3\2\2" + + "\2\u0080\u0081\7\n\2\2\u0081%\3\2\2\2\u0082\u0087\5(\25\2\u0083\u0087" + + "\5*\26\2\u0084\u0087\5,\27\2\u0085\u0087\3\2\2\2\u0086\u0082\3\2\2\2\u0086" + + "\u0083\3\2\2\2\u0086\u0084\3\2\2\2\u0086\u0085\3\2\2\2\u0087\'\3\2\2\2" + + "\u0088\u0089\7\n\2\2\u0089)\3\2\2\2\u008a\u008b\7\13\2\2\u008b+\3\2\2" + + "\2\u008c\u008d\7\f\2\2\u008d-\3\2\2\2\u008e\u008f\5\60\31\2\u008f\u0090" + + "\7\b\2\2\u0090\u0091\5\62\32\2\u0091/\3\2\2\2\u0092\u0093\7\n\2\2\u0093" + + "\61\3\2\2\2\u0094\u0095\7\f\2\2\u0095\63\3\2\2\2\13\67>BIMTr|\u0086"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + private static final String[] _LITERAL_NAMES = { + null, "'.id '", "'.int_stack_count '", "'.string_stack_count '", "'.int_var_count '", + "'.string_var_count '", "':'" + }; + private static final String[] _SYMBOLIC_NAMES = { + null, null, null, null, null, null, null, "NEWLINE", "INT", "QSTRING", + "IDENTIFIER", "COMMENT", "WS" + }; + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + static + { + RuntimeMetaData.checkVersion("4.6", RuntimeMetaData.VERSION); + } + + static + { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) + { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) + { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) + { + tokenNames[i] = ""; + } + } + } + + static + { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) + { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } + + public rs2asmParser(TokenStream input) + { + super(input); + _interp = new ParserATNSimulator(this, _ATN, _decisionToDFA, _sharedContextCache); + } + + @Override + @Deprecated + public String[] getTokenNames() + { + return tokenNames; + } + + @Override + public String[] getRuleNames() + { + return ruleNames; + } + + @Override + + public Vocabulary getVocabulary() + { + return VOCABULARY; + } + + @Override + public String getSerializedATN() + { + return _serializedATN; + } + + @Override + public String getGrammarFileName() + { + return "rs2asm.g4"; + } + + @Override + public ATN getATN() + { + return _ATN; + } + + public final ProgContext prog() throws RecognitionException + { + ProgContext _localctx = new ProgContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_prog); + int _la; + try + { + enterOuterAlt(_localctx, 1); + { + setState(53); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la == NEWLINE) + { + { + { + setState(50); + match(NEWLINE); + } + } + setState(55); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(64); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__0) | (1L << T__1) | (1L << T__2) | (1L << T__3) | (1L << T__4))) != 0)) + { + { + { + setState(56); + header(); + setState(58); + _errHandler.sync(this); + _la = _input.LA(1); + do + { + { + { + setState(57); + match(NEWLINE); + } + } + setState(60); + _errHandler.sync(this); + _la = _input.LA(1); + } while (_la == NEWLINE); + } + } + setState(66); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(73); + _errHandler.sync(this); + _la = _input.LA(1); + do + { + { + { + setState(67); + line(); + setState(69); + _errHandler.sync(this); + _la = _input.LA(1); + do + { + { + { + setState(68); + match(NEWLINE); + } + } + setState(71); + _errHandler.sync(this); + _la = _input.LA(1); + } while (_la == NEWLINE); + } + } + setState(75); + _errHandler.sync(this); + _la = _input.LA(1); + } while (_la == INT || _la == IDENTIFIER); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final HeaderContext header() throws RecognitionException + { + HeaderContext _localctx = new HeaderContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_header); + try + { + setState(82); + _errHandler.sync(this); + switch (_input.LA(1)) + { + case T__0: + enterOuterAlt(_localctx, 1); + { + setState(77); + id(); + } + break; + case T__1: + enterOuterAlt(_localctx, 2); + { + setState(78); + int_stack_count(); + } + break; + case T__2: + enterOuterAlt(_localctx, 3); + { + setState(79); + string_stack_count(); + } + break; + case T__3: + enterOuterAlt(_localctx, 4); + { + setState(80); + int_var_count(); + } + break; + case T__4: + enterOuterAlt(_localctx, 5); + { + setState(81); + string_var_count(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final IdContext id() throws RecognitionException + { + IdContext _localctx = new IdContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_id); + try + { + enterOuterAlt(_localctx, 1); + { + setState(84); + match(T__0); + setState(85); + id_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Int_stack_countContext int_stack_count() throws RecognitionException + { + Int_stack_countContext _localctx = new Int_stack_countContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_int_stack_count); + try + { + enterOuterAlt(_localctx, 1); + { + setState(87); + match(T__1); + setState(88); + int_stack_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final String_stack_countContext string_stack_count() throws RecognitionException + { + String_stack_countContext _localctx = new String_stack_countContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_string_stack_count); + try + { + enterOuterAlt(_localctx, 1); + { + setState(90); + match(T__2); + setState(91); + string_stack_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Int_var_countContext int_var_count() throws RecognitionException + { + Int_var_countContext _localctx = new Int_var_countContext(_ctx, getState()); + enterRule(_localctx, 10, RULE_int_var_count); + try + { + enterOuterAlt(_localctx, 1); + { + setState(93); + match(T__3); + setState(94); + int_var_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final String_var_countContext string_var_count() throws RecognitionException + { + String_var_countContext _localctx = new String_var_countContext(_ctx, getState()); + enterRule(_localctx, 12, RULE_string_var_count); + try + { + enterOuterAlt(_localctx, 1); + { + setState(96); + match(T__4); + setState(97); + string_var_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Id_valueContext id_value() throws RecognitionException + { + Id_valueContext _localctx = new Id_valueContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_id_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(99); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Int_stack_valueContext int_stack_value() throws RecognitionException + { + Int_stack_valueContext _localctx = new Int_stack_valueContext(_ctx, getState()); + enterRule(_localctx, 16, RULE_int_stack_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(101); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final String_stack_valueContext string_stack_value() throws RecognitionException + { + String_stack_valueContext _localctx = new String_stack_valueContext(_ctx, getState()); + enterRule(_localctx, 18, RULE_string_stack_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(103); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Int_var_valueContext int_var_value() throws RecognitionException + { + Int_var_valueContext _localctx = new Int_var_valueContext(_ctx, getState()); + enterRule(_localctx, 20, RULE_int_var_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(105); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final String_var_valueContext string_var_value() throws RecognitionException + { + String_var_valueContext _localctx = new String_var_valueContext(_ctx, getState()); + enterRule(_localctx, 22, RULE_string_var_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(107); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final LineContext line() throws RecognitionException + { + LineContext _localctx = new LineContext(_ctx, getState()); + enterRule(_localctx, 24, RULE_line); + try + { + setState(112); + _errHandler.sync(this); + switch (getInterpreter().adaptivePredict(_input, 6, _ctx)) + { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(109); + instruction(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(110); + label(); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(111); + switch_lookup(); + } + break; + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final InstructionContext instruction() throws RecognitionException + { + InstructionContext _localctx = new InstructionContext(_ctx, getState()); + enterRule(_localctx, 26, RULE_instruction); + try + { + enterOuterAlt(_localctx, 1); + { + setState(114); + instruction_name(); + setState(115); + instruction_operand(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final LabelContext label() throws RecognitionException + { + LabelContext _localctx = new LabelContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_label); + try + { + enterOuterAlt(_localctx, 1); + { + setState(117); + match(IDENTIFIER); + setState(118); + match(T__5); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Instruction_nameContext instruction_name() throws RecognitionException + { + Instruction_nameContext _localctx = new Instruction_nameContext(_ctx, getState()); + enterRule(_localctx, 30, RULE_instruction_name); + try + { + setState(122); + _errHandler.sync(this); + switch (_input.LA(1)) + { + case IDENTIFIER: + enterOuterAlt(_localctx, 1); + { + setState(120); + name_string(); + } + break; + case INT: + enterOuterAlt(_localctx, 2); + { + setState(121); + name_opcode(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Name_stringContext name_string() throws RecognitionException + { + Name_stringContext _localctx = new Name_stringContext(_ctx, getState()); + enterRule(_localctx, 32, RULE_name_string); + try + { + enterOuterAlt(_localctx, 1); + { + setState(124); + match(IDENTIFIER); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Name_opcodeContext name_opcode() throws RecognitionException + { + Name_opcodeContext _localctx = new Name_opcodeContext(_ctx, getState()); + enterRule(_localctx, 34, RULE_name_opcode); + try + { + enterOuterAlt(_localctx, 1); + { + setState(126); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Instruction_operandContext instruction_operand() throws RecognitionException + { + Instruction_operandContext _localctx = new Instruction_operandContext(_ctx, getState()); + enterRule(_localctx, 36, RULE_instruction_operand); + try + { + setState(132); + _errHandler.sync(this); + switch (_input.LA(1)) + { + case INT: + enterOuterAlt(_localctx, 1); + { + setState(128); + operand_int(); + } + break; + case QSTRING: + enterOuterAlt(_localctx, 2); + { + setState(129); + operand_qstring(); + } + break; + case IDENTIFIER: + enterOuterAlt(_localctx, 3); + { + setState(130); + operand_label(); + } + break; + case NEWLINE: + enterOuterAlt(_localctx, 4); + { + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Operand_intContext operand_int() throws RecognitionException + { + Operand_intContext _localctx = new Operand_intContext(_ctx, getState()); + enterRule(_localctx, 38, RULE_operand_int); + try + { + enterOuterAlt(_localctx, 1); + { + setState(134); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Operand_qstringContext operand_qstring() throws RecognitionException + { + Operand_qstringContext _localctx = new Operand_qstringContext(_ctx, getState()); + enterRule(_localctx, 40, RULE_operand_qstring); + try + { + enterOuterAlt(_localctx, 1); + { + setState(136); + match(QSTRING); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Operand_labelContext operand_label() throws RecognitionException + { + Operand_labelContext _localctx = new Operand_labelContext(_ctx, getState()); + enterRule(_localctx, 42, RULE_operand_label); + try + { + enterOuterAlt(_localctx, 1); + { + setState(138); + match(IDENTIFIER); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Switch_lookupContext switch_lookup() throws RecognitionException + { + Switch_lookupContext _localctx = new Switch_lookupContext(_ctx, getState()); + enterRule(_localctx, 44, RULE_switch_lookup); + try + { + enterOuterAlt(_localctx, 1); + { + setState(140); + switch_key(); + setState(141); + match(T__5); + setState(142); + switch_value(); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Switch_keyContext switch_key() throws RecognitionException + { + Switch_keyContext _localctx = new Switch_keyContext(_ctx, getState()); + enterRule(_localctx, 46, RULE_switch_key); + try + { + enterOuterAlt(_localctx, 1); + { + setState(144); + match(INT); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public final Switch_valueContext switch_value() throws RecognitionException + { + Switch_valueContext _localctx = new Switch_valueContext(_ctx, getState()); + enterRule(_localctx, 48, RULE_switch_value); + try + { + enterOuterAlt(_localctx, 1); + { + setState(146); + match(IDENTIFIER); + } + } + catch (RecognitionException re) + { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally + { + exitRule(); + } + return _localctx; + } + + public static class ProgContext extends ParserRuleContext + { + public ProgContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public List NEWLINE() + { + return getTokens(rs2asmParser.NEWLINE); + } + + public TerminalNode NEWLINE(int i) + { + return getToken(rs2asmParser.NEWLINE, i); + } + + public List header() + { + return getRuleContexts(HeaderContext.class); + } + + public HeaderContext header(int i) + { + return getRuleContext(HeaderContext.class, i); + } + + public List line() + { + return getRuleContexts(LineContext.class); + } + + public LineContext line(int i) + { + return getRuleContext(LineContext.class, i); + } + + @Override + public int getRuleIndex() + { + return RULE_prog; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterProg(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitProg(this); + } + } + } + + public static class HeaderContext extends ParserRuleContext + { + public HeaderContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public IdContext id() + { + return getRuleContext(IdContext.class, 0); + } + + public Int_stack_countContext int_stack_count() + { + return getRuleContext(Int_stack_countContext.class, 0); + } + + public String_stack_countContext string_stack_count() + { + return getRuleContext(String_stack_countContext.class, 0); + } + + public Int_var_countContext int_var_count() + { + return getRuleContext(Int_var_countContext.class, 0); + } + + public String_var_countContext string_var_count() + { + return getRuleContext(String_var_countContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_header; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterHeader(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitHeader(this); + } + } + } + + public static class IdContext extends ParserRuleContext + { + public IdContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Id_valueContext id_value() + { + return getRuleContext(Id_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_id; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterId(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitId(this); + } + } + } + + public static class Int_stack_countContext extends ParserRuleContext + { + public Int_stack_countContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Int_stack_valueContext int_stack_value() + { + return getRuleContext(Int_stack_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_int_stack_count; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInt_stack_count(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInt_stack_count(this); + } + } + } + + public static class String_stack_countContext extends ParserRuleContext + { + public String_stack_countContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public String_stack_valueContext string_stack_value() + { + return getRuleContext(String_stack_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_string_stack_count; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterString_stack_count(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitString_stack_count(this); + } + } + } + + public static class Int_var_countContext extends ParserRuleContext + { + public Int_var_countContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Int_var_valueContext int_var_value() + { + return getRuleContext(Int_var_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_int_var_count; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInt_var_count(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInt_var_count(this); + } + } + } + + public static class String_var_countContext extends ParserRuleContext + { + public String_var_countContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public String_var_valueContext string_var_value() + { + return getRuleContext(String_var_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_string_var_count; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterString_var_count(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitString_var_count(this); + } + } + } + + public static class Id_valueContext extends ParserRuleContext + { + public Id_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_id_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterId_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitId_value(this); + } + } + } + + public static class Int_stack_valueContext extends ParserRuleContext + { + public Int_stack_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_int_stack_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInt_stack_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInt_stack_value(this); + } + } + } + + public static class String_stack_valueContext extends ParserRuleContext + { + public String_stack_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_string_stack_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterString_stack_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitString_stack_value(this); + } + } + } + + public static class Int_var_valueContext extends ParserRuleContext + { + public Int_var_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_int_var_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInt_var_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInt_var_value(this); + } + } + } + + public static class String_var_valueContext extends ParserRuleContext + { + public String_var_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_string_var_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterString_var_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitString_var_value(this); + } + } + } + + public static class LineContext extends ParserRuleContext + { + public LineContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public InstructionContext instruction() + { + return getRuleContext(InstructionContext.class, 0); + } + + public LabelContext label() + { + return getRuleContext(LabelContext.class, 0); + } + + public Switch_lookupContext switch_lookup() + { + return getRuleContext(Switch_lookupContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_line; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterLine(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitLine(this); + } + } + } + + public static class InstructionContext extends ParserRuleContext + { + public InstructionContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Instruction_nameContext instruction_name() + { + return getRuleContext(Instruction_nameContext.class, 0); + } + + public Instruction_operandContext instruction_operand() + { + return getRuleContext(Instruction_operandContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_instruction; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInstruction(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInstruction(this); + } + } + } + + public static class LabelContext extends ParserRuleContext + { + public LabelContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode IDENTIFIER() + { + return getToken(rs2asmParser.IDENTIFIER, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_label; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterLabel(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitLabel(this); + } + } + } + + public static class Instruction_nameContext extends ParserRuleContext + { + public Instruction_nameContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Name_stringContext name_string() + { + return getRuleContext(Name_stringContext.class, 0); + } + + public Name_opcodeContext name_opcode() + { + return getRuleContext(Name_opcodeContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_instruction_name; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInstruction_name(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInstruction_name(this); + } + } + } + + public static class Name_stringContext extends ParserRuleContext + { + public Name_stringContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode IDENTIFIER() + { + return getToken(rs2asmParser.IDENTIFIER, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_name_string; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterName_string(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitName_string(this); + } + } + } + + public static class Name_opcodeContext extends ParserRuleContext + { + public Name_opcodeContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_name_opcode; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterName_opcode(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitName_opcode(this); + } + } + } + + public static class Instruction_operandContext extends ParserRuleContext + { + public Instruction_operandContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Operand_intContext operand_int() + { + return getRuleContext(Operand_intContext.class, 0); + } + + public Operand_qstringContext operand_qstring() + { + return getRuleContext(Operand_qstringContext.class, 0); + } + + public Operand_labelContext operand_label() + { + return getRuleContext(Operand_labelContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_instruction_operand; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterInstruction_operand(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitInstruction_operand(this); + } + } + } + + public static class Operand_intContext extends ParserRuleContext + { + public Operand_intContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_operand_int; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterOperand_int(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitOperand_int(this); + } + } + } + + public static class Operand_qstringContext extends ParserRuleContext + { + public Operand_qstringContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode QSTRING() + { + return getToken(rs2asmParser.QSTRING, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_operand_qstring; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterOperand_qstring(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitOperand_qstring(this); + } + } + } + + public static class Operand_labelContext extends ParserRuleContext + { + public Operand_labelContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode IDENTIFIER() + { + return getToken(rs2asmParser.IDENTIFIER, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_operand_label; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterOperand_label(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitOperand_label(this); + } + } + } + + public static class Switch_lookupContext extends ParserRuleContext + { + public Switch_lookupContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public Switch_keyContext switch_key() + { + return getRuleContext(Switch_keyContext.class, 0); + } + + public Switch_valueContext switch_value() + { + return getRuleContext(Switch_valueContext.class, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_switch_lookup; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterSwitch_lookup(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitSwitch_lookup(this); + } + } + } + + public static class Switch_keyContext extends ParserRuleContext + { + public Switch_keyContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode INT() + { + return getToken(rs2asmParser.INT, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_switch_key; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterSwitch_key(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitSwitch_key(this); + } + } + } + + public static class Switch_valueContext extends ParserRuleContext + { + public Switch_valueContext(ParserRuleContext parent, int invokingState) + { + super(parent, invokingState); + } + + public TerminalNode IDENTIFIER() + { + return getToken(rs2asmParser.IDENTIFIER, 0); + } + + @Override + public int getRuleIndex() + { + return RULE_switch_value; + } + + @Override + public void enterRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).enterSwitch_value(this); + } + } + + @Override + public void exitRule(ParseTreeListener listener) + { + if (listener instanceof rs2asmListener) + { + ((rs2asmListener) listener).exitSwitch_value(this); + } + } + } +} \ No newline at end of file diff --git a/cache/src/main/java/net/runelite/cache/script/rs2asm.tokens b/cache/src/main/java/net/runelite/cache/script/rs2asm.tokens new file mode 100644 index 0000000000..de505a0dff --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/rs2asm.tokens @@ -0,0 +1,18 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +NEWLINE=7 +INT=8 +QSTRING=9 +IDENTIFIER=10 +COMMENT=11 +WS=12 +'.id '=1 +'.int_stack_count '=2 +'.string_stack_count '=3 +'.int_var_count '=4 +'.string_var_count '=5 +':'=6 diff --git a/cache/src/main/java/net/runelite/cache/script/rs2asmLexer.tokens b/cache/src/main/java/net/runelite/cache/script/rs2asmLexer.tokens new file mode 100644 index 0000000000..de505a0dff --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/script/rs2asmLexer.tokens @@ -0,0 +1,18 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +NEWLINE=7 +INT=8 +QSTRING=9 +IDENTIFIER=10 +COMMENT=11 +WS=12 +'.id '=1 +'.int_stack_count '=2 +'.string_stack_count '=3 +'.int_var_count '=4 +'.string_var_count '=5 +':'=6 diff --git a/cache/src/main/java/net/runelite/cache/util/ScriptVarType.java b/cache/src/main/java/net/runelite/cache/util/ScriptVarType.java index 2ff3bdf515..bda13d8c3a 100644 --- a/cache/src/main/java/net/runelite/cache/util/ScriptVarType.java +++ b/cache/src/main/java/net/runelite/cache/util/ScriptVarType.java @@ -82,19 +82,18 @@ public enum ScriptVarType } } - public static ScriptVarType forCharKey(char key) - { - return keyToTypeMap.get(key); - } - /** * The character used when encoding or decoding types. */ private final char keyChar; - /** * The full name of the var type. */ private final String fullName; + public static ScriptVarType forCharKey(char key) + { + return keyToTypeMap.get(key); + } + } diff --git a/cache/src/main/resources/djb2.properties b/cache/src/main/resources/djb2.properties index 4d635ffaae..87c3bb8a6c 100644 --- a/cache/src/main/resources/djb2.properties +++ b/cache/src/main/resources/djb2.properties @@ -1,1403 +1,1403 @@ -#Wed Nov 29 15:08:06 PST 2017 --1863637185=miscgraphics,4 --1863637184=miscgraphics,5 --1863637187=miscgraphics,2 --1863637186=miscgraphics,3 --1863637181=miscgraphics,8 --1863637180=miscgraphics,9 --440204630=alls fairy in love n war --1863637183=miscgraphics,6 --1253085654=scorpia_dances --1863637182=miscgraphics,7 -1356196826=backvmid3 --751102526=high seas -1356196825=backvmid2 --1863637189=miscgraphics,0 -549358875=camelot --1863637188=miscgraphics,1 --1619800378=emotes_locked,19 --1619800379=emotes_locked,18 -792536868=understanding -1356196824=backvmid1 --1773559904=title_mute --342013218=p11_full -1523653533=pirates of peril -696768774=harmony --1701556831=magicoff2,27 -281586976=orb_icon,3 -286265996=ready for battle --1701556832=magicoff2,26 -281586977=orb_icon,4 --645977478=scape scared --1701556833=magicoff2,25 -281586978=orb_icon,5 --1701556834=magicoff2,24 --1701556830=magicoff2,28 --1052794696=dance of death --1619800383=emotes_locked,14 --1619800384=emotes_locked,13 --1619800385=emotes_locked,12 --1556842207=sl_flags --1619800386=emotes_locked,11 -1614826739=mapletree --1619800387=emotes_locked,10 --1701556835=magicoff2,23 --1701556836=magicoff2,22 -1363656441=serenade --1701556837=magicoff2,21 -3089326=door --1701556838=magicoff2,20 --1619800380=emotes_locked,17 --1619800381=emotes_locked,16 --1619800382=emotes_locked,15 --1037172987=tomorrow -825974316=melodrama -281586973=orb_icon,0 -281586974=orb_icon,1 -281586975=orb_icon,2 --1701556864=magicoff2,15 --1701556865=magicoff2,14 --1701556866=magicoff2,13 -1509400204=sarcophagus --1701556867=magicoff2,12 --1701556860=magicoff2,19 --1701556861=magicoff2,18 --1701556862=magicoff2,17 -1868377358=lower_depths --1701556863=magicoff2,16 -1086036315=reggae2 --1220755677=hermit --1544597765=sl_stars --1619800350=emotes_locked,26 --1619800351=emotes_locked,25 --1636062434=tex_brown --1619800352=emotes_locked,24 --1619800353=emotes_locked,23 --2122174648=back to life --1619800354=emotes_locked,22 --1701556868=magicoff2,11 --1619800355=emotes_locked,21 --1701556869=magicoff2,10 --1619800356=emotes_locked,20 -1619539773=side_icons,7 -1121239524=scape wild -368271413=diango's little helpers -1619539772=side_icons,6 -1619539775=side_icons,9 -1619539774=side_icons,8 --1938172321=miscgraphics2,0 -1619539771=side_icons,5 -1619539770=side_icons,4 --926977577=the enchanter -1202794514=doorways -1343649581=schools out --1097177625=q8_full -756012174=wornicons,4 -756012173=wornicons,3 -756012176=wornicons,6 -756012175=wornicons,5 -756012170=wornicons,0 -827249681=ogre the top -756012172=wornicons,2 -756012171=wornicons,1 --1741764817=poles apart -756012178=wornicons,8 --1938172320=miscgraphics2,1 -756012177=wornicons,7 --43136286=the last shanty -756012179=wornicons,9 -837131331=mapback --1938172313=miscgraphics2,8 -907740319=the depths --1938172312=miscgraphics2,9 --2099722614=cave of beasts --1938172317=miscgraphics2,4 --1938172316=miscgraphics2,5 --1938172315=miscgraphics2,6 --1938172314=miscgraphics2,7 -1619539769=side_icons,3 --1367483767=cavern --1938172319=miscgraphics2,2 -50474489=treestump --1938172318=miscgraphics2,3 -1619539766=side_icons,0 -1619539768=side_icons,2 -1619539767=side_icons,1 --607416954=prayeron,11 -1837251043=melzars maze --607416953=prayeron,12 -1318893900=have an ice day --607416952=prayeron,13 --1701556800=magicoff2,37 --607416951=prayeron,14 --1701556801=magicoff2,36 --56804840=woodland --607416950=prayeron,15 -1120636327=scape cave -358884868=button_red --1701556806=magicoff2,31 --1701556807=magicoff2,30 --1701556802=magicoff2,35 --1701556803=magicoff2,34 --1701556804=magicoff2,33 --1858265682=monster melee --1701556805=magicoff2,32 --649484675=land of the dwarves --1789903512=golden touch -1328851780=close_buttons,6 -1080306793=shayzien_march --1339126929=damage -4820960=monkey sadness --1951786153=bone dance -1328851781=close_buttons,7 -939546513=forlorn_homestead --1019905269=shadowland --607416955=prayeron,10 --2048535896=pheasant peasant --1025233830=monkey madness -1121125956=scape soft --1562452687=etcetera -1969878996=emotes,47 -1969878994=emotes,45 --1228279872=ge_icons,3 -1969878995=emotes,46 --1228279871=ge_icons,4 -1969878992=emotes,43 --1228279870=ge_icons,5 -1969878993=emotes,44 -1959803992=invback -399048409=mage arena -1969878990=emotes,41 --47057524=lasting -1969878991=emotes,42 --1228279875=ge_icons,0 --1228279874=ge_icons,1 --1228279873=ge_icons,2 --1860080918=inspiration -1997757502=redstone2 -1997757503=redstone3 --1060046352=tribal2 -1997757501=redstone1 --1701556829=magicoff2,29 -1890607210=magicon2,37 -1890607211=magicon2,38 -1890607212=magicon2,39 --1408684838=ascent --848436598=fishing --119984250=combaticons2,17 --119984251=combaticons2,16 -1769177816=jungle island --119984252=combaticons2,15 --119984253=combaticons2,14 --119984254=combaticons2,13 --119984255=combaticons2,12 --119984256=combaticons2,11 --119984257=combaticons2,10 -1890607205=magicon2,32 -1890607206=magicon2,33 -1890607207=magicon2,34 -1890607208=magicon2,35 -135141185=zeah_combat -1890607209=magicon2,36 --180851958=norse code -112903447=water -922007495=talking forest --672706748=miracle dance --1110089645=lament --1237461365=grotto -1890607203=magicon2,30 -1890607204=magicon2,31 --1073910849=mirror --988841056=still night --1857025509=sunburn --468596910=easter jig -796868952=major miner --1066798491=trawler -1640556978=wonderous --1624274920=emperor -740093634=find my way -1890607238=magicon2,44 -1890607239=magicon2,45 -3559837=tick --1839713245=sideicons -1029455878=hells bells -1890607234=magicon2,40 -1890607235=magicon2,41 -1890607236=magicon2,42 -1890607237=magicon2,43 -1503566841=forbidden --895763669=spooky --276138668=ham attack -500433071=combaticons,5 --1021014225=catch me if you can -500433070=combaticons,4 -500433073=combaticons,7 -500433072=combaticons,6 -500433075=combaticons,9 -1533565119=mind over matter -500433074=combaticons,8 -2025958358=emotes_locked,4 -500433066=combaticons,0 -2025958357=emotes_locked,3 -862821975=far away --1228392498=artistry -2025958356=emotes_locked,2 -500433068=combaticons,2 -2025958355=emotes_locked,1 -500433067=combaticons,1 -1085444827=refresh -500433069=combaticons,3 -2025958359=emotes_locked,5 -1427043851=on the up -2025958354=emotes_locked,0 -1316697938=whistle -347955347=venture -1959211608=mapfunction,77 -1959211609=mapfunction,78 -881850881=the chosen -1959211601=mapfunction,70 -1959211602=mapfunction,71 -584643951=lost soul -582140282=rising damp -1959211603=mapfunction,72 -1740872686=soulfall -1959211604=mapfunction,73 -1959211605=mapfunction,74 --119984248=combaticons2,19 -1959211606=mapfunction,75 --119984249=combaticons2,18 -1609255038=slither and thither -82917947=sarim's vermin -1959211607=mapfunction,76 -1728911401=natural --1189743137=duel arena -108698078=roof2 -214634021=head to head -2025958361=emotes_locked,7 --448773288=isle of everywhere -2025958360=emotes_locked,6 --1869996941=titlebox --338347745=showdown --2075972251=long ago -2025958363=emotes_locked,9 -2025958362=emotes_locked,8 --1487589606=7th realm --1253087691=garden --2133902017=zeah_farming --492926285=impetuous -3314014=lair -907815588=the desert --1960860275=barbarianism -1890607241=magicon2,47 -1890607242=magicon2,48 -1890607243=magicon2,49 --919642451=jungle bells -795515487=underground -561438836=fountain --1418827919=illusive --634763748=fruits de mer -1890607240=magicon2,46 -1694458038=large_button -1393517697=bandit camp -1959211632=mapfunction,80 -1884773718=magicoff2,2 -1884773719=magicoff2,3 -1884773716=magicoff2,0 -2121900771=backtop1 -1884773717=magicoff2,1 --103077377=gnomeball --1947119982=blistering barnacles -828650857=autumn voyage -92909147=alone -1691516951=undead dungeon -122265833=expecting --1320617626=dunjun -1959211633=mapfunction,81 -1959211634=mapfunction,82 -1959211635=mapfunction,83 -1959211636=mapfunction,84 -1959211637=mapfunction,85 -777534707=army of darkness -1959211638=mapfunction,86 -1959211639=mapfunction,87 -1884773721=magicoff2,5 -1884773722=magicoff2,6 -1884773720=magicoff2,4 -1959211640=mapfunction,88 -1959211641=mapfunction,89 --327707013=anywhere -1884773725=magicoff2,9 -1884773723=magicoff2,7 -1884773724=magicoff2,8 -1116844876=incantation --728886272=temple of light -685934899=in the clink --1237289460=grumpy -1945133711=inferno -466902883=strange place --418223472=phasmatys -817472004=zombiism -106578554=zeah_book,0 -106578555=zeah_book,1 -1509070203=eagle peak --485932799=expedition -1171923143=emotes,8 --675357975=attack1 -1171923144=emotes,9 -1959211610=mapfunction,79 --675357974=attack2 --675357973=attack3 --675357972=attack4 --675357971=attack5 -40246002=masquerade --675357970=attack6 --734206983=arrival --1980407601=sea shanty xmas -284435223=pharoah's tomb --148552909=down below -1171923141=emotes,6 -1171923142=emotes,7 -1171923140=emotes,5 --1077789440=mellow --710537653=kingdom -1171923138=emotes,3 -1171923139=emotes,4 -1171923136=emotes,1 --2098286081=venture2 -1171923137=emotes,2 -1171923135=emotes,0 --1094248165=sigmunds showdown --271106892=rat a tat tat -3288564=keys --143163121=ham fisted --900633031=medieval -944208821=life's a beach\! --1228279453=riverside --1666444059=combaticons,10 -825919125=options_icons,24 -825919126=options_icons,25 -1179379180=the trade parade --1666444057=combaticons,12 -825919123=options_icons,22 --1666444058=combaticons,11 -825919124=options_icons,23 -825919121=options_icons,20 -1884768169=magicoff,32 -825919122=options_icons,21 -1884768167=magicoff,30 -1884768168=magicoff,31 -1318818808=chainmail -582031337=intrepid -783525419=beetle juice -432605856=untouchable --969918857=neverland -79789174=narnode's theme --705938181=zealot -117588=web --1666444051=combaticons,18 --1666444052=combaticons,17 -1687654733=troubled --1666444050=combaticons,19 --1666444055=combaticons,14 -825919129=options_icons,28 --1666444056=combaticons,13 --1666444053=combaticons,16 -825919127=options_icons,26 --1666444054=combaticons,15 -825919128=options_icons,27 -1320694328=magical journey -364185053=roll the bones --1254483584=jungly1 -981183822=right on track --1254483583=jungly2 --1254483582=jungly3 -3075958=dark --2038936746=deep down -1512143976=everlasting fire --1392319985=beyond -46273615=tale of keldagrim --651951461=goblin game -3522941=save -104084791=mossy -1250935993=the monsters below -794539501=garden of summer -1814277765=elven mist -2110556093=the golem --1475251658=where eagles lair -1884768143=magicoff,27 -1884768144=magicoff,28 -1884768141=magicoff,25 -1529837717=bubble and squeak -1884768142=magicoff,26 --1679325940=technology -1884768145=magicoff,29 --826562194=troubled_waters -1884768140=magicoff,24 --1359348243=painting1 -1267356434=the power of tears --1359348242=painting2 --860755690=jungle hunt -1884768138=magicoff,22 --1197347961=magic magic magic -1884768139=magicoff,23 -1134405764=hypnotized -1959211539=mapfunction,50 -1381363755=my arms journey --1644401602=complication -1959211540=mapfunction,51 -1959211541=mapfunction,52 -1884768136=magicoff,20 -1959211542=mapfunction,53 -2111304827=warning_icons,0 -1884768137=magicoff,21 --440187560=zogre dance -1959211543=mapfunction,54 -2111304828=warning_icons,1 --2002535437=corridors of power -825919130=options_icons,29 -580384095=jungle troubles -1301622585=slice of station -1959211544=mapfunction,55 -2111304829=warning_icons,2 --1294172031=escape --1309477156=expanse --1526067851=alternative root -2124773424=dynasty -1743765602=leftarrow --1482676188=romancing the crone --1891851953=island of the trolls -736457293=small_button_pressed --1106172890=letter -986170990=dreamstate -1959211545=mapfunction,56 -1959211546=mapfunction,57 -1765722413=spirits of elid -1959211547=mapfunction,58 -1959211548=mapfunction,59 --2075333010=lonesome -3314400=lava -1355033875=worldmap_icon,1 -1814357716=knightmare -1690742645=nox_irae -94935104=cross --1249495153=frogland --1642689926=athletes foot -107944162=quest -1355033874=worldmap_icon,0 --2130741313=joy of the hunt --28982081=labyrinth -250959119=marooned --1522984472=altar_ego -1326424637=the lost melody --1779111734=arabique --398925062=sea shanty2 -1884768110=magicoff,15 -1884768111=magicoff,16 -1817249074=woe of the wyvern -1884768114=magicoff,19 -1884768112=magicoff,17 --1624760229=emotion -1884768113=magicoff,18 --353951458=attention -279431252=garden of autumn -422652266=small_button -1884768107=magicoff,12 -1884768108=magicoff,13 -375695247=the far side -1884768105=magicoff,10 -1884768106=magicoff,11 --528864109=crystal sword -1884768109=magicoff,14 -1959211570=mapfunction,60 --158141423=prayeron,7 --158141424=prayeron,6 -1959211571=mapfunction,61 --158141421=prayeron,9 -1959211572=mapfunction,62 --158141422=prayeron,8 -1959211573=mapfunction,63 -1959211574=mapfunction,64 -688840255=piscarilius_sigil -1959211575=mapfunction,65 -1959211576=mapfunction,66 -1170407052=headicons_prayer -1959211577=mapfunction,67 --324496873=soundscape --1418445703=tex_red -1961540869=wornicons,10 --1028580907=that_sullen_hall -397136995=elfpainting --2092714094=haunted mine -1959211578=mapfunction,68 -1959211579=mapfunction,69 --158141429=prayeron,1 --158141427=prayeron,3 --140492390=bunny_sugar_rush --158141428=prayeron,2 --158141425=prayeron,5 -1827366203=righteousness --649601274=darkness_in_the_depths --158141426=prayeron,4 -1961540870=wornicons,11 --158141430=prayeron,0 -910299584=principality --734028978=arrow,1 --734028979=arrow,0 -790067275=garden of spring -35762567=workshop --1095396929=competition -96463963=egypt --1154441378=jollyr --1685231711=cave background --2078908549=time out --1172405897=wildwood --170561624=spookyjungle -2110231453=mod_icons -2061491048=shining --1063411723=tremble -94627585=chest --520702427=ice melody -1346720899=backleft1 --607416919=prayeron,25 --607416918=prayeron,26 -900197712=staticons,6 --607416917=prayeron,27 -900197713=staticons,7 --607416916=prayeron,28 -900197710=staticons,4 --607416915=prayeron,29 -900197711=staticons,5 -957931606=courage -900197714=staticons,8 -900197715=staticons,9 --720253066=the other side --1655721374=prayeroff,31 --607416921=prayeron,23 --1655721375=prayeroff,30 --607416920=prayeron,24 --710515142=the mad mole --1350228392=stratosphere --1666438445=combaticons2,3 --1666438446=combaticons2,2 --1666438443=combaticons2,5 --1666438444=combaticons2,4 --1106570438=legion --1666438441=combaticons2,7 --1666438442=combaticons2,6 -1398587265=flute salad --1666438440=combaticons2,8 -837223705=mapedge -900197709=staticons,3 --243680393=peng_emotes,7 -900197707=staticons,1 -378300078=everlasting -900197708=staticons,2 --944748869=witching --243680396=peng_emotes,4 --1335336992=logo_deadman_mode --1368714737=small_button_blue --795140435=wander --243680397=peng_emotes,3 --243680394=peng_emotes,6 --1666438447=combaticons2,1 --243680395=peng_emotes,5 --1666438448=combaticons2,0 --243680398=peng_emotes,2 --243680399=peng_emotes,1 --607416924=prayeron,20 -900197706=staticons,0 --607416923=prayeron,21 --607416922=prayeron,22 -3016376=bark --89244313=romper chomper -346288985=dorgeshun deep -1585002399=magicon,21 --1725263140=chef surprize -1585002398=magicon,20 --993528987=making waves --628963539=ham and seek --1666438439=combaticons2,9 --333224315=baroque --1236252722=prime time -280241284=waking dream --564582358=distant land -115411843=castlewars -1513246078=al kharid --1377700863=unknown land -1264132816=miles away -1185785872=barbassault_icons,3 -1185785873=barbassault_icons,4 --1655721397=prayeroff,29 --1655721398=prayeroff,28 -1185785874=barbassault_icons,5 --1655721399=prayeroff,27 -1185785875=barbassault_icons,6 -1711341885=fight or flight -1185785870=barbassault_icons,1 -1185785871=barbassault_icons,2 -3530505=sire --309570839=pick_and_shovel -647234089=distillery hilarity --127408236=gnome_village_party -1337378554=backbase2 -109757537=stars -1337378553=backbase1 -109757538=start -819884325=wilderness3 -1328851775=close_buttons,1 -658759958=side_background -1328851774=close_buttons,0 --782211141=wonder -1328851777=close_buttons,3 --1995718284=wall_white -819884324=wilderness2 -1328851776=close_buttons,2 -1328851779=close_buttons,5 --943885542=scape hunter -1328851778=close_buttons,4 -1185785869=barbassault_icons,0 --607416949=prayeron,16 --607416948=prayeron,17 --1779684630=rune essence --607416947=prayeron,18 --607416946=prayeron,19 -1119460311=bandos battalion --967559823=creature cruelty --1904094243=zeah_fishing --1913214770=wilderness -460367020=village -825919161=options_icons,39 -1585002375=magicon,18 -94839810=coins -1585002376=magicon,19 -825919160=options_icons,38 --1282090556=faerie --521895311=the adventurer -788399136=tree spirits --1902858744=beneath_the_stronghold --356730043=pirates of penance -1185785876=barbassault_icons,7 -825919158=options_icons,36 -825919159=options_icons,37 -825919156=options_icons,34 --1455241861=victory is mine -825919157=options_icons,35 --1333874720=side_icons,17 -825919154=options_icons,32 -825919155=options_icons,33 -825919152=options_icons,30 -825919153=options_icons,31 -1437805631=chatback --1623296531=ground scape -685190118=in the brine -1366257555=nightfall -110327241=theme --1333874725=side_icons,12 --1333874726=side_icons,11 --1333874727=side_icons,10 --1333874721=side_icons,16 --1333874722=side_icons,15 --1124681475=darkly_altared --8976533=throne of the demon --1333874723=side_icons,14 --1333874724=side_icons,13 --1989106719=assault and battery -1958759012=greatness -1057075019=b12_full -1143353537=chain of command --51091830=desert voyage --1073927447=mirage --91048728=number_button -3165239=gaol --1380919269=breeze -445640248=rugged_terrain -106079=key --655784411=overlay_multiway --1025835715=backright2 --1025835716=backright1 -1120933843=scape main -3225350=iban --956253112=title fight --123912401=la mort -1585002367=magicon,10 --2128736428=startgame -1585002368=magicon,11 --925031874=royale -1585002369=magicon,12 -1585002370=magicon,13 -1585002373=magicon,16 -1585002374=magicon,17 --1307116191=superstition -1585002371=magicon,14 -1585002372=magicon,15 --587569902=path of peril -3392903=null --1601127242=inadequacy -2136330800=staticons2,0 -2136330801=staticons2,1 -344336468=grip of the talon -2136330804=staticons2,4 -2136330805=staticons2,5 -2136330802=staticons2,2 -2136330803=staticons2,3 -1960215130=barking mad --43712789=scape original -621171714=cellar song -1585002461=magicon,41 -1585002462=magicon,42 -1585002460=magicon,40 -111485446=upass -2136330808=staticons2,8 -694847251=in the manor -2136330809=staticons2,9 -2136330806=staticons2,6 -2136330807=staticons2,7 --1385847955=rightarrow -1343200077=the slayer -1585002465=magicon,45 -1585002466=magicon,46 -1585002463=magicon,43 -1585002464=magicon,44 -1585002469=magicon,49 --95571520=volcanic vikings -1585002467=magicon,47 -1585002468=magicon,48 --1032629963=shipwrecked -93330745=aztec --881372797=tabs,1 -1377351472=oriental -121641580=headicons_hint --881372798=tabs,0 -1585002438=magicon,39 --1661605940=elfwood --607416893=prayeron,30 --822106577=jungle island xmas --607416892=prayeron,31 -332368736=mad eadgar -1585002432=magicon,33 --143368781=side_background_right -1585002433=magicon,34 --1081494434=malady -1585002430=magicon,31 -1585002431=magicon,32 -1585002436=magicon,37 -1585002437=magicon,38 -1585002434=magicon,35 -1585002435=magicon,36 --1938171360=miscgraphics3,0 --1059680853=trinity -781557721=dies_irae --1938171359=miscgraphics3,1 -497375231=stillness --1938171358=miscgraphics3,2 --1938171357=miscgraphics3,3 --960709976=dogs of war -2129339089=magicon,1 -2129339088=magicon,0 -755433248=headicons_pk -108392383=regal --213632750=waterfall --1367706280=canvas -73828649=settlement -848123561=into the abyss -478781900=last stand -1339486127=the shadow --1055503808=roc and roll -837204902=mapdots -950484242=compass --1082154559=fanfare -747848680=nether_realm -788224888=dead quiet -1532279978=monarch waltz --149029727=side_background_left1 -812947089=fanfare2 --149029726=side_background_left2 -1006643748=high spirits --2136059388=starlight -2122572442=the tower --1998869913=spooky2 -1411067174=gnome village2 --2065077267=wild side -812947090=fanfare3 -1585002429=magicon,30 -1294629755=on the wing -2097127567=monkey badness --2032107216=sojourn -1020264019=pest control -3237038=info -1473393027=fe fi fo fum --1686202291=upper_depths -3540994=stop -1742080803=darkwood -740392969=little cave of horrors --158379532=prayerglow --691855347=in between --200702983=the noble rodent -1652745754=forgotten --1895307673=hitmark,3 --1895307674=hitmark,2 --1895307675=hitmark,1 --1895307676=hitmark,0 --1895307670=hitmark,6 --1895307671=hitmark,5 --1895307672=hitmark,4 -1968917071=bone dry --850506182=trawler minor -197029040=mapscene --808772318=in the pits --1165315580=looking back --1763090403=scape_ape --1938177931=miscgraphics,11 --1938177932=miscgraphics,10 -1936130561=thrall_of_the_serpent -1258863383=yesteryear -1994744000=slice of silent movie --1691854169=dead can dance -1585002407=magicon,29 -1585002405=magicon,27 -1585002406=magicon,28 -271319484=frostbite --499867199=meridian -1585002400=magicon,22 --1938177930=miscgraphics,12 --84626226=mudskipper melody -1585002403=magicon,25 -1585002404=magicon,26 -3641802=wall -1585002401=magicon,23 -1585002402=magicon,24 --606457701=wolf mountain -1276599785=button_brown -1969878899=emotes,13 -1969878897=emotes,11 -1969878898=emotes,12 -1969878896=emotes,10 --1938177928=miscgraphics,14 --907669678=brew hoo hoo --1938177929=miscgraphics,13 -72999866=subterranea -619237947=the galleon --1764950404=scape sad -295831445=heart and mind -908430134=dangerous road -738888631=tradebacking --174800339=verdana_11pt_regular -686705631=lightwalk --601591436=side_background_bottom --1479412376=the navigator --359173459=zamorak zoo -744536246=null and void --1701556798=magicoff2,39 --1701556799=magicoff2,38 --1396384012=bamboo --200388662=lighthouse -133626717=suspicious --810515425=voyage -3061973=crag -1802291895=big chords --1661619479=elfwall -113315621=wood2 -1813041183=steelborder2,0 -429244831=slug a bug ball -1813041184=steelborder2,1 --1658386264=shining_spirit -738909086=chamber -526264239=verdana_13pt_regular --877351859=temple -2142215577=the mollusc menace -1124498189=warpath --2136649922=no way out --339706871=grimly_fiendish -547534551=wrath and ruin -544229147=lore and order -3327206=load -1610073470=lovakengj_sigil --419218284=long way home --662489856=food for thought -1306461568=stagnant --1662171955=elfdoor --1043985601=meddling kids -947464074=titlebutton --1309055712=exposed --1487348923=ambient jungle --1829469821=lament of meiyerditch -233203434=leftarrow_small --1216167350=dangerous -114464611=railings --1106574323=legend --1701556767=magicoff2,49 --999707515=time to mine --1701556768=magicoff2,48 -2129339097=magicon,9 -2129339096=magicon,8 -1959211510=mapfunction,42 -2129339095=magicon,7 -1959211511=mapfunction,43 -2129339094=magicon,6 -2129339093=magicon,5 -2129339092=magicon,4 -2129339091=magicon,3 -2129339090=magicon,2 -3522472=saga --1701556769=magicoff2,47 --544722449=rellekka -1033441676=tribal background -1915718129=the desolate isle -1890607150=magicon2,19 -1787618597=stranded -1717999087=forgettable melody -1959211512=mapfunction,44 --243680400=peng_emotes,0 -1959211513=mapfunction,45 -1959211514=mapfunction,46 -1959211515=mapfunction,47 -1959211516=mapfunction,48 -1959211517=mapfunction,49 -1705947058=the cellar dwellers -1216634785=landlubber -1884768198=magicoff,40 --1588113323=the rogues den -1884768199=magicoff,41 --905842564=serene --607599698=prayeroff,2 --607599699=prayeroff,1 -1890607142=magicon2,11 -1389384362=monkey trouble -1890607143=magicon2,12 -1890607144=magicon2,13 -1890607145=magicon2,14 -1890607146=magicon2,15 -1890607147=magicon2,16 -1890607148=magicon2,17 -1966766798=mausoleum -1890607149=magicon2,18 -1808345541=armadyl alliance -290391725=options_slider,7 -1890607141=magicon2,10 -290391722=options_slider,4 --607599696=prayeroff,4 -290391721=options_slider,3 --607599697=prayeroff,3 -290391724=options_slider,6 --607599694=prayeroff,6 -290391723=options_slider,5 --607599695=prayeroff,5 --607599692=prayeroff,8 --607599693=prayeroff,7 -290391720=options_slider,2 --607599691=prayeroff,9 -1334775925=chat_background --1779127378=arabian2 --1779127377=arabian3 -528722471=island life -1890607175=magicon2,23 -1890607176=magicon2,24 -949634504=mouse trap -1890607177=magicon2,25 -1890607178=magicon2,26 -1890607179=magicon2,27 -3327403=logo -290391719=options_slider,1 -1092249049=storm brew -290391718=options_slider,0 -404357804=everywhere -1890607172=magicon2,20 -1890607173=magicon2,21 -951530772=contest -1890607174=magicon2,22 --1701556776=magicoff2,40 -1884768176=magicoff,39 --395250469=corporal punishment -1749113330=newbie melody -1884768174=magicoff,37 -1884768175=magicoff,38 --1701556772=magicoff2,44 --1701556773=magicoff2,43 --1701556774=magicoff2,42 --1701556775=magicoff2,41 --858121616=tzhaar -1884768172=magicoff,35 -1884768173=magicoff,36 -666772244=combat_shield -1884768170=magicoff,33 -1884768171=magicoff,34 -1959211509=mapfunction,41 -1639695510=mapmarker --1661748240=friends_icons --552301350=knightly --1918044851=mastermindless --1701556770=magicoff2,46 --1701556771=magicoff2,45 -201526300=corporealbeast -1959211508=mapfunction,40 -105001967=nomad --70910145=clickcross,3 --1737914947=mapfunction,5 -1801745440=staticons2,11 --70910146=clickcross,2 --865479038=tribal --1737914946=mapfunction,6 -1801745441=staticons2,12 --70910147=clickcross,1 --1737914945=mapfunction,7 -1801745442=staticons2,13 --70910148=clickcross,0 --1737914944=mapfunction,8 -1801745443=staticons2,14 --1737914943=mapfunction,9 -1801745444=staticons2,15 -1801745445=staticons2,16 -1801745446=staticons2,17 --1877545169=land down under -93921962=books --1737914949=mapfunction,3 --1737914948=mapfunction,4 --1655721428=prayeroff,19 --2136884405=title.jpg --1655721429=prayeroff,18 -1584819628=magicoff,6 -1584819629=magicoff,7 -437480876=voodoo cult -1584819624=magicoff,2 -1584819625=magicoff,3 -1584819626=magicoff,4 --1737914952=mapfunction,0 -1584819627=magicoff,5 --1737914951=mapfunction,1 -124995564=harmony2 -1584819622=magicoff,0 -1801745439=staticons2,10 -1584819623=magicoff,1 -346263512=dorgeshun city --1665011705=down and out -1890607180=magicon2,28 -1890607181=magicon2,29 -1417471781=titlescroll -1959211446=mapfunction,20 -1956141536=options_radio_buttons,0 -736568812=ballad of enchantment --1737914950=mapfunction,2 -1959211447=mapfunction,21 -1959211448=mapfunction,22 -1959211449=mapfunction,23 --1890130256=morytania --70910141=clickcross,7 --70910142=clickcross,6 -1956141539=options_radio_buttons,3 --70910143=clickcross,5 -1956141538=options_radio_buttons,2 -284766976=splendour --70910144=clickcross,4 -1956141537=options_radio_buttons,1 -196677638=the quizmaster -530068296=overture --1123094568=sl_button --700552779=hosidius_sigil --614076819=sad meadow -1956141543=options_radio_buttons,7 -1956141542=options_radio_buttons,6 -1956141541=options_radio_buttons,5 -1956141540=options_radio_buttons,4 -1584819631=magicoff,9 -1846633612=gnome village --2128560371=sl_back -1969878905=emotes,19 -306819362=crystal castle -1584819630=magicoff,8 -1969878903=emotes,17 -303737220=options_icons,7 -1969878904=emotes,18 -1969878901=emotes,15 --78220817=devils_may_care -1969878902=emotes,16 --40521666=dimension x -1969878900=emotes,14 -673424924=the lunar isle -789609582=brimstail's scales -303737222=options_icons,9 -303737221=options_icons,8 -1959211415=mapfunction,10 -3059343=coil -1959211416=mapfunction,11 -1959211417=mapfunction,12 --1256560486=last_man_standing -1959211418=mapfunction,13 -336238005=rightarrow_small -1959211419=mapfunction,14 --677662361=forever --1655721430=prayeroff,17 --1655721431=prayeroff,16 --1655721432=prayeroff,15 -1959211420=mapfunction,15 -1959211663=mapfunction,90 -1959211421=mapfunction,16 -1959211422=mapfunction,17 -1959211423=mapfunction,18 -303737217=options_icons,4 -303737216=options_icons,3 -303737215=options_icons,2 --1665005042=funny bunnies -303737214=options_icons,1 -303737219=options_icons,6 -303737218=options_icons,5 -303737213=options_icons,0 -95997798=we are the fairies -2001751835=desert heat -1959211424=mapfunction,19 --1655721437=prayeroff,10 -687938017=clanwars --1776024210=desolate_mage --650944128=strength of saradomin --1655721433=prayeroff,14 -1160873524=aye car rum ba --1655721434=prayeroff,13 --1655721435=prayeroff,12 --1081314499=marble --1655721436=prayeroff,11 -1097075475=reset,0 -1959211477=mapfunction,30 --693313916=warriors guild -3506388=roof -1959211478=mapfunction,31 -1097075476=reset,1 --2134967800=dagannoth dawn --985763247=planks -1959211479=mapfunction,32 -1999746381=fenkenstrain's refrain -898010371=garden of winter -359174830=rat hunt -1959211482=mapfunction,35 -686441581=lightness -1959211483=mapfunction,36 -1959211484=mapfunction,37 -1959211485=mapfunction,38 -1959211486=mapfunction,39 -2023201035=dwarf theme -1959211480=mapfunction,33 -1959211481=mapfunction,34 --1065532022=combatboxes,1 --1065532021=combatboxes,2 --1065532020=combatboxes,3 -1867160429=old_tiles -394756979=scape santa -25205919=elfroof2 --663428071=dangerous way --1065532023=combatboxes,0 -1959211450=mapfunction,24 --895939599=spirit -1959211451=mapfunction,25 -1959211452=mapfunction,26 -1959211453=mapfunction,27 -1959211454=mapfunction,28 -1959211455=mapfunction,29 --275310687=undercurrent -212205923=goblin village --303898981=faithless --1381531001=tomb raider -260940912=marzipan -1343267530=backhmid1 -1343267531=backhmid2 -1097468315=horizon --1655721404=prayeroff,22 -623451622=kourend_the_magnificent --1655721405=prayeroff,21 --1655721406=prayeroff,20 --1655721400=prayeroff,26 --313384067=p12_full --1655721401=prayeroff,25 --1655721402=prayeroff,24 --1655721403=prayeroff,23 -95848451=dream -1966781751=maws_jaws_claws --995428255=parade -95734525=method of madness --1308064877=hitmarks -1030045177=mutant medley -1333034828=blackmark -851641665=davy jones locker -417793574=scrollbar -1346720900=backleft2 -1884768206=magicoff,48 -1884768207=magicoff,49 -1801140808=fangs for the memory -1884768204=magicoff,46 -1345432055=pinball wizard -1884768205=magicoff,47 --783693496=dance of the undead -1274780903=chompy hunt -465278529=the lost tribe --1666437481=combaticons3,6 -2032696205=cabin fever --1666437480=combaticons3,7 -1825640471=borderland -415928477=zeah_mining -1884768202=magicoff,44 --607599700=prayeroff,0 -1884768203=magicoff,45 -1884768200=magicoff,42 -1884768201=magicoff,43 --1666437487=combaticons3,0 --1666437486=combaticons3,1 -813726263=crystal cave -1235442953=pathways -518814479=lullaby --1666437483=combaticons3,4 -1585007985=magicon2,7 --1666437482=combaticons3,5 -1585007986=magicon2,8 --1666437485=combaticons3,2 -1585007987=magicon2,9 --1666437484=combaticons3,3 -104080482=moody -1969878967=emotes,39 --665666447=work work work -1364992651=evil bobs island -1969878965=emotes,37 -1227328817=verdana_15pt_regular -1969878966=emotes,38 -1969878963=emotes,35 -1581724013=monkey business -1969878964=emotes,36 --74307138=miscellania -1969878961=emotes,33 -1969878962=emotes,34 -1969878960=emotes,32 -1131171307=wayward --1154558416=sl_arrows -529929957=overpass -1258058669=huffman --694094064=adventure -1171698653=orb_xp,1 -1171698652=orb_xp,0 -1171698655=orb_xp,3 -1171698654=orb_xp,2 -1802171733=arceuus_sigil -221109227=tears of guthix --1661754893=elfroof --1666437478=combaticons3,9 -1124565314=warrior -1969878958=emotes,30 -1969878959=emotes,31 --934797897=reggae -2110260221=the genie --1666437479=combaticons3,8 -104257585=mummy --1923924724=sworddecor,0 -2136325196=staticons,17 --1332194002=background -2136164423=homescape --1923924721=sworddecor,3 -2136325194=staticons,15 --1923924722=sworddecor,2 --873564465=tiptoe -2136325195=staticons,16 --1923924723=sworddecor,1 -123560953=espionage -2136325192=staticons,13 -2136325193=staticons,14 -1038911415=gnome king -2136325190=staticons,11 -2136325191=staticons,12 -1854274741=karamja jam -1969878989=emotes,40 --1242708793=glyphs -563269755=the terrible tower -1650323088=twilight --12868552=sea shanty -289742397=book of spells -1880989696=dragontooth island -2136325189=staticons,10 -110873=pen --454421102=out of the deep -825919099=options_icons,19 -825919097=options_icons,17 -825919098=options_icons,18 --1349119470=cursed -1585007978=magicon2,0 -825919095=options_icons,15 -1585007979=magicon2,1 -825919096=options_icons,16 -825919093=options_icons,13 -825919094=options_icons,14 -825919091=options_icons,11 -825919092=options_icons,12 --895977880=sphinx --874529881=city of the dead -825919090=options_icons,10 -1585007981=magicon2,3 -1585007982=magicon2,4 -1585007983=magicon2,5 --1618729246=body parts -1585007984=magicon2,6 -1585007980=magicon2,2 -1086075866=shayzien_sigil -2103661451=jester minute --911346307=steelborder,2 --1809781334=button_brown_big --911346306=steelborder,3 --816227352=vision --911346309=steelborder,0 -109407595=shine --911346308=steelborder,1 --119954464=combaticons3,12 --119954465=combaticons3,11 --1846853118=armageddon --119954462=combaticons3,14 --119954463=combaticons3,13 --119954460=combaticons3,16 --908183966=scarab --119954461=combaticons3,15 --282886672=home sweet home -103666243=march -1969878929=emotes,22 -1643875326=fire and brimstone -1969878927=emotes,20 --119954466=combaticons3,10 -1969878928=emotes,21 --839455633=close quarters -941457503=way of the enchanter --1453405761=mor-ul-rek --415134015=have a blast --1658514874=floating free -1213477442=chickened out --1619800349=emotes_locked,27 --599680631=fear and loathing --1081041422=insect queen --1268786147=forest --119954459=combaticons3,17 --119954457=combaticons3,19 --119954458=combaticons3,18 --850395529=trouble brewing --1773920521=cave of the goblins --771284962=claustrophobia -1310729739=bankbuttons,2 -1306691868=upcoming -1465443077=over to nardah -1310729744=bankbuttons,7 -1310729743=bankbuttons,6 -1310729742=bankbuttons,5 -1234827707=deep wildy --90350772=xenophobe --750127868=arabian -1310729741=bankbuttons,4 -1310729740=bankbuttons,3 -1041911129=waterlogged -108875897=runes -1447063382=barb wire --378865792=magic dance -285466503=overlay_duel -1814287296=zeah_magic -1976894499=down to earth -1969878936=emotes,29 -1969878934=emotes,27 --1567437308=deadlands -1969878935=emotes,28 -1310729738=bankbuttons,1 -1969878932=emotes,25 --957019274=too many cooks -1310729737=bankbuttons,0 -2092627105=silence -1969878933=emotes,26 -1969878930=emotes,23 -1969878931=emotes,24 --901674570=well of voyage +#Wed Nov 29 15:08:06 PST 2017 +-1863637185=miscgraphics,4 +-1863637184=miscgraphics,5 +-1863637187=miscgraphics,2 +-1863637186=miscgraphics,3 +-1863637181=miscgraphics,8 +-1863637180=miscgraphics,9 +-440204630=alls fairy in love n war +-1863637183=miscgraphics,6 +-1253085654=scorpia_dances +-1863637182=miscgraphics,7 +1356196826=backvmid3 +-751102526=high seas +1356196825=backvmid2 +-1863637189=miscgraphics,0 +549358875=camelot +-1863637188=miscgraphics,1 +-1619800378=emotes_locked,19 +-1619800379=emotes_locked,18 +792536868=understanding +1356196824=backvmid1 +-1773559904=title_mute +-342013218=p11_full +1523653533=pirates of peril +696768774=harmony +-1701556831=magicoff2,27 +281586976=orb_icon,3 +286265996=ready for battle +-1701556832=magicoff2,26 +281586977=orb_icon,4 +-645977478=scape scared +-1701556833=magicoff2,25 +281586978=orb_icon,5 +-1701556834=magicoff2,24 +-1701556830=magicoff2,28 +-1052794696=dance of death +-1619800383=emotes_locked,14 +-1619800384=emotes_locked,13 +-1619800385=emotes_locked,12 +-1556842207=sl_flags +-1619800386=emotes_locked,11 +1614826739=mapletree +-1619800387=emotes_locked,10 +-1701556835=magicoff2,23 +-1701556836=magicoff2,22 +1363656441=serenade +-1701556837=magicoff2,21 +3089326=door +-1701556838=magicoff2,20 +-1619800380=emotes_locked,17 +-1619800381=emotes_locked,16 +-1619800382=emotes_locked,15 +-1037172987=tomorrow +825974316=melodrama +281586973=orb_icon,0 +281586974=orb_icon,1 +281586975=orb_icon,2 +-1701556864=magicoff2,15 +-1701556865=magicoff2,14 +-1701556866=magicoff2,13 +1509400204=sarcophagus +-1701556867=magicoff2,12 +-1701556860=magicoff2,19 +-1701556861=magicoff2,18 +-1701556862=magicoff2,17 +1868377358=lower_depths +-1701556863=magicoff2,16 +1086036315=reggae2 +-1220755677=hermit +-1544597765=sl_stars +-1619800350=emotes_locked,26 +-1619800351=emotes_locked,25 +-1636062434=tex_brown +-1619800352=emotes_locked,24 +-1619800353=emotes_locked,23 +-2122174648=back to life +-1619800354=emotes_locked,22 +-1701556868=magicoff2,11 +-1619800355=emotes_locked,21 +-1701556869=magicoff2,10 +-1619800356=emotes_locked,20 +1619539773=side_icons,7 +1121239524=scape wild +368271413=diango's little helpers +1619539772=side_icons,6 +1619539775=side_icons,9 +1619539774=side_icons,8 +-1938172321=miscgraphics2,0 +1619539771=side_icons,5 +1619539770=side_icons,4 +-926977577=the enchanter +1202794514=doorways +1343649581=schools out +-1097177625=q8_full +756012174=wornicons,4 +756012173=wornicons,3 +756012176=wornicons,6 +756012175=wornicons,5 +756012170=wornicons,0 +827249681=ogre the top +756012172=wornicons,2 +756012171=wornicons,1 +-1741764817=poles apart +756012178=wornicons,8 +-1938172320=miscgraphics2,1 +756012177=wornicons,7 +-43136286=the last shanty +756012179=wornicons,9 +837131331=mapback +-1938172313=miscgraphics2,8 +907740319=the depths +-1938172312=miscgraphics2,9 +-2099722614=cave of beasts +-1938172317=miscgraphics2,4 +-1938172316=miscgraphics2,5 +-1938172315=miscgraphics2,6 +-1938172314=miscgraphics2,7 +1619539769=side_icons,3 +-1367483767=cavern +-1938172319=miscgraphics2,2 +50474489=treestump +-1938172318=miscgraphics2,3 +1619539766=side_icons,0 +1619539768=side_icons,2 +1619539767=side_icons,1 +-607416954=prayeron,11 +1837251043=melzars maze +-607416953=prayeron,12 +1318893900=have an ice day +-607416952=prayeron,13 +-1701556800=magicoff2,37 +-607416951=prayeron,14 +-1701556801=magicoff2,36 +-56804840=woodland +-607416950=prayeron,15 +1120636327=scape cave +358884868=button_red +-1701556806=magicoff2,31 +-1701556807=magicoff2,30 +-1701556802=magicoff2,35 +-1701556803=magicoff2,34 +-1701556804=magicoff2,33 +-1858265682=monster melee +-1701556805=magicoff2,32 +-649484675=land of the dwarves +-1789903512=golden touch +1328851780=close_buttons,6 +1080306793=shayzien_march +-1339126929=damage +4820960=monkey sadness +-1951786153=bone dance +1328851781=close_buttons,7 +939546513=forlorn_homestead +-1019905269=shadowland +-607416955=prayeron,10 +-2048535896=pheasant peasant +-1025233830=monkey madness +1121125956=scape soft +-1562452687=etcetera +1969878996=emotes,47 +1969878994=emotes,45 +-1228279872=ge_icons,3 +1969878995=emotes,46 +-1228279871=ge_icons,4 +1969878992=emotes,43 +-1228279870=ge_icons,5 +1969878993=emotes,44 +1959803992=invback +399048409=mage arena +1969878990=emotes,41 +-47057524=lasting +1969878991=emotes,42 +-1228279875=ge_icons,0 +-1228279874=ge_icons,1 +-1228279873=ge_icons,2 +-1860080918=inspiration +1997757502=redstone2 +1997757503=redstone3 +-1060046352=tribal2 +1997757501=redstone1 +-1701556829=magicoff2,29 +1890607210=magicon2,37 +1890607211=magicon2,38 +1890607212=magicon2,39 +-1408684838=ascent +-848436598=fishing +-119984250=combaticons2,17 +-119984251=combaticons2,16 +1769177816=jungle island +-119984252=combaticons2,15 +-119984253=combaticons2,14 +-119984254=combaticons2,13 +-119984255=combaticons2,12 +-119984256=combaticons2,11 +-119984257=combaticons2,10 +1890607205=magicon2,32 +1890607206=magicon2,33 +1890607207=magicon2,34 +1890607208=magicon2,35 +135141185=zeah_combat +1890607209=magicon2,36 +-180851958=norse code +112903447=water +922007495=talking forest +-672706748=miracle dance +-1110089645=lament +-1237461365=grotto +1890607203=magicon2,30 +1890607204=magicon2,31 +-1073910849=mirror +-988841056=still night +-1857025509=sunburn +-468596910=easter jig +796868952=major miner +-1066798491=trawler +1640556978=wonderous +-1624274920=emperor +740093634=find my way +1890607238=magicon2,44 +1890607239=magicon2,45 +3559837=tick +-1839713245=sideicons +1029455878=hells bells +1890607234=magicon2,40 +1890607235=magicon2,41 +1890607236=magicon2,42 +1890607237=magicon2,43 +1503566841=forbidden +-895763669=spooky +-276138668=ham attack +500433071=combaticons,5 +-1021014225=catch me if you can +500433070=combaticons,4 +500433073=combaticons,7 +500433072=combaticons,6 +500433075=combaticons,9 +1533565119=mind over matter +500433074=combaticons,8 +2025958358=emotes_locked,4 +500433066=combaticons,0 +2025958357=emotes_locked,3 +862821975=far away +-1228392498=artistry +2025958356=emotes_locked,2 +500433068=combaticons,2 +2025958355=emotes_locked,1 +500433067=combaticons,1 +1085444827=refresh +500433069=combaticons,3 +2025958359=emotes_locked,5 +1427043851=on the up +2025958354=emotes_locked,0 +1316697938=whistle +347955347=venture +1959211608=mapfunction,77 +1959211609=mapfunction,78 +881850881=the chosen +1959211601=mapfunction,70 +1959211602=mapfunction,71 +584643951=lost soul +582140282=rising damp +1959211603=mapfunction,72 +1740872686=soulfall +1959211604=mapfunction,73 +1959211605=mapfunction,74 +-119984248=combaticons2,19 +1959211606=mapfunction,75 +-119984249=combaticons2,18 +1609255038=slither and thither +82917947=sarim's vermin +1959211607=mapfunction,76 +1728911401=natural +-1189743137=duel arena +108698078=roof2 +214634021=head to head +2025958361=emotes_locked,7 +-448773288=isle of everywhere +2025958360=emotes_locked,6 +-1869996941=titlebox +-338347745=showdown +-2075972251=long ago +2025958363=emotes_locked,9 +2025958362=emotes_locked,8 +-1487589606=7th realm +-1253087691=garden +-2133902017=zeah_farming +-492926285=impetuous +3314014=lair +907815588=the desert +-1960860275=barbarianism +1890607241=magicon2,47 +1890607242=magicon2,48 +1890607243=magicon2,49 +-919642451=jungle bells +795515487=underground +561438836=fountain +-1418827919=illusive +-634763748=fruits de mer +1890607240=magicon2,46 +1694458038=large_button +1393517697=bandit camp +1959211632=mapfunction,80 +1884773718=magicoff2,2 +1884773719=magicoff2,3 +1884773716=magicoff2,0 +2121900771=backtop1 +1884773717=magicoff2,1 +-103077377=gnomeball +-1947119982=blistering barnacles +828650857=autumn voyage +92909147=alone +1691516951=undead dungeon +122265833=expecting +-1320617626=dunjun +1959211633=mapfunction,81 +1959211634=mapfunction,82 +1959211635=mapfunction,83 +1959211636=mapfunction,84 +1959211637=mapfunction,85 +777534707=army of darkness +1959211638=mapfunction,86 +1959211639=mapfunction,87 +1884773721=magicoff2,5 +1884773722=magicoff2,6 +1884773720=magicoff2,4 +1959211640=mapfunction,88 +1959211641=mapfunction,89 +-327707013=anywhere +1884773725=magicoff2,9 +1884773723=magicoff2,7 +1884773724=magicoff2,8 +1116844876=incantation +-728886272=temple of light +685934899=in the clink +-1237289460=grumpy +1945133711=inferno +466902883=strange place +-418223472=phasmatys +817472004=zombiism +106578554=zeah_book,0 +106578555=zeah_book,1 +1509070203=eagle peak +-485932799=expedition +1171923143=emotes,8 +-675357975=attack1 +1171923144=emotes,9 +1959211610=mapfunction,79 +-675357974=attack2 +-675357973=attack3 +-675357972=attack4 +-675357971=attack5 +40246002=masquerade +-675357970=attack6 +-734206983=arrival +-1980407601=sea shanty xmas +284435223=pharoah's tomb +-148552909=down below +1171923141=emotes,6 +1171923142=emotes,7 +1171923140=emotes,5 +-1077789440=mellow +-710537653=kingdom +1171923138=emotes,3 +1171923139=emotes,4 +1171923136=emotes,1 +-2098286081=venture2 +1171923137=emotes,2 +1171923135=emotes,0 +-1094248165=sigmunds showdown +-271106892=rat a tat tat +3288564=keys +-143163121=ham fisted +-900633031=medieval +944208821=life's a beach\! +-1228279453=riverside +-1666444059=combaticons,10 +825919125=options_icons,24 +825919126=options_icons,25 +1179379180=the trade parade +-1666444057=combaticons,12 +825919123=options_icons,22 +-1666444058=combaticons,11 +825919124=options_icons,23 +825919121=options_icons,20 +1884768169=magicoff,32 +825919122=options_icons,21 +1884768167=magicoff,30 +1884768168=magicoff,31 +1318818808=chainmail +582031337=intrepid +783525419=beetle juice +432605856=untouchable +-969918857=neverland +79789174=narnode's theme +-705938181=zealot +117588=web +-1666444051=combaticons,18 +-1666444052=combaticons,17 +1687654733=troubled +-1666444050=combaticons,19 +-1666444055=combaticons,14 +825919129=options_icons,28 +-1666444056=combaticons,13 +-1666444053=combaticons,16 +825919127=options_icons,26 +-1666444054=combaticons,15 +825919128=options_icons,27 +1320694328=magical journey +364185053=roll the bones +-1254483584=jungly1 +981183822=right on track +-1254483583=jungly2 +-1254483582=jungly3 +3075958=dark +-2038936746=deep down +1512143976=everlasting fire +-1392319985=beyond +46273615=tale of keldagrim +-651951461=goblin game +3522941=save +104084791=mossy +1250935993=the monsters below +794539501=garden of summer +1814277765=elven mist +2110556093=the golem +-1475251658=where eagles lair +1884768143=magicoff,27 +1884768144=magicoff,28 +1884768141=magicoff,25 +1529837717=bubble and squeak +1884768142=magicoff,26 +-1679325940=technology +1884768145=magicoff,29 +-826562194=troubled_waters +1884768140=magicoff,24 +-1359348243=painting1 +1267356434=the power of tears +-1359348242=painting2 +-860755690=jungle hunt +1884768138=magicoff,22 +-1197347961=magic magic magic +1884768139=magicoff,23 +1134405764=hypnotized +1959211539=mapfunction,50 +1381363755=my arms journey +-1644401602=complication +1959211540=mapfunction,51 +1959211541=mapfunction,52 +1884768136=magicoff,20 +1959211542=mapfunction,53 +2111304827=warning_icons,0 +1884768137=magicoff,21 +-440187560=zogre dance +1959211543=mapfunction,54 +2111304828=warning_icons,1 +-2002535437=corridors of power +825919130=options_icons,29 +580384095=jungle troubles +1301622585=slice of station +1959211544=mapfunction,55 +2111304829=warning_icons,2 +-1294172031=escape +-1309477156=expanse +-1526067851=alternative root +2124773424=dynasty +1743765602=leftarrow +-1482676188=romancing the crone +-1891851953=island of the trolls +736457293=small_button_pressed +-1106172890=letter +986170990=dreamstate +1959211545=mapfunction,56 +1959211546=mapfunction,57 +1765722413=spirits of elid +1959211547=mapfunction,58 +1959211548=mapfunction,59 +-2075333010=lonesome +3314400=lava +1355033875=worldmap_icon,1 +1814357716=knightmare +1690742645=nox_irae +94935104=cross +-1249495153=frogland +-1642689926=athletes foot +107944162=quest +1355033874=worldmap_icon,0 +-2130741313=joy of the hunt +-28982081=labyrinth +250959119=marooned +-1522984472=altar_ego +1326424637=the lost melody +-1779111734=arabique +-398925062=sea shanty2 +1884768110=magicoff,15 +1884768111=magicoff,16 +1817249074=woe of the wyvern +1884768114=magicoff,19 +1884768112=magicoff,17 +-1624760229=emotion +1884768113=magicoff,18 +-353951458=attention +279431252=garden of autumn +422652266=small_button +1884768107=magicoff,12 +1884768108=magicoff,13 +375695247=the far side +1884768105=magicoff,10 +1884768106=magicoff,11 +-528864109=crystal sword +1884768109=magicoff,14 +1959211570=mapfunction,60 +-158141423=prayeron,7 +-158141424=prayeron,6 +1959211571=mapfunction,61 +-158141421=prayeron,9 +1959211572=mapfunction,62 +-158141422=prayeron,8 +1959211573=mapfunction,63 +1959211574=mapfunction,64 +688840255=piscarilius_sigil +1959211575=mapfunction,65 +1959211576=mapfunction,66 +1170407052=headicons_prayer +1959211577=mapfunction,67 +-324496873=soundscape +-1418445703=tex_red +1961540869=wornicons,10 +-1028580907=that_sullen_hall +397136995=elfpainting +-2092714094=haunted mine +1959211578=mapfunction,68 +1959211579=mapfunction,69 +-158141429=prayeron,1 +-158141427=prayeron,3 +-140492390=bunny_sugar_rush +-158141428=prayeron,2 +-158141425=prayeron,5 +1827366203=righteousness +-649601274=darkness_in_the_depths +-158141426=prayeron,4 +1961540870=wornicons,11 +-158141430=prayeron,0 +910299584=principality +-734028978=arrow,1 +-734028979=arrow,0 +790067275=garden of spring +35762567=workshop +-1095396929=competition +96463963=egypt +-1154441378=jollyr +-1685231711=cave background +-2078908549=time out +-1172405897=wildwood +-170561624=spookyjungle +2110231453=mod_icons +2061491048=shining +-1063411723=tremble +94627585=chest +-520702427=ice melody +1346720899=backleft1 +-607416919=prayeron,25 +-607416918=prayeron,26 +900197712=staticons,6 +-607416917=prayeron,27 +900197713=staticons,7 +-607416916=prayeron,28 +900197710=staticons,4 +-607416915=prayeron,29 +900197711=staticons,5 +957931606=courage +900197714=staticons,8 +900197715=staticons,9 +-720253066=the other side +-1655721374=prayeroff,31 +-607416921=prayeron,23 +-1655721375=prayeroff,30 +-607416920=prayeron,24 +-710515142=the mad mole +-1350228392=stratosphere +-1666438445=combaticons2,3 +-1666438446=combaticons2,2 +-1666438443=combaticons2,5 +-1666438444=combaticons2,4 +-1106570438=legion +-1666438441=combaticons2,7 +-1666438442=combaticons2,6 +1398587265=flute salad +-1666438440=combaticons2,8 +837223705=mapedge +900197709=staticons,3 +-243680393=peng_emotes,7 +900197707=staticons,1 +378300078=everlasting +900197708=staticons,2 +-944748869=witching +-243680396=peng_emotes,4 +-1335336992=logo_deadman_mode +-1368714737=small_button_blue +-795140435=wander +-243680397=peng_emotes,3 +-243680394=peng_emotes,6 +-1666438447=combaticons2,1 +-243680395=peng_emotes,5 +-1666438448=combaticons2,0 +-243680398=peng_emotes,2 +-243680399=peng_emotes,1 +-607416924=prayeron,20 +900197706=staticons,0 +-607416923=prayeron,21 +-607416922=prayeron,22 +3016376=bark +-89244313=romper chomper +346288985=dorgeshun deep +1585002399=magicon,21 +-1725263140=chef surprize +1585002398=magicon,20 +-993528987=making waves +-628963539=ham and seek +-1666438439=combaticons2,9 +-333224315=baroque +-1236252722=prime time +280241284=waking dream +-564582358=distant land +115411843=castlewars +1513246078=al kharid +-1377700863=unknown land +1264132816=miles away +1185785872=barbassault_icons,3 +1185785873=barbassault_icons,4 +-1655721397=prayeroff,29 +-1655721398=prayeroff,28 +1185785874=barbassault_icons,5 +-1655721399=prayeroff,27 +1185785875=barbassault_icons,6 +1711341885=fight or flight +1185785870=barbassault_icons,1 +1185785871=barbassault_icons,2 +3530505=sire +-309570839=pick_and_shovel +647234089=distillery hilarity +-127408236=gnome_village_party +1337378554=backbase2 +109757537=stars +1337378553=backbase1 +109757538=start +819884325=wilderness3 +1328851775=close_buttons,1 +658759958=side_background +1328851774=close_buttons,0 +-782211141=wonder +1328851777=close_buttons,3 +-1995718284=wall_white +819884324=wilderness2 +1328851776=close_buttons,2 +1328851779=close_buttons,5 +-943885542=scape hunter +1328851778=close_buttons,4 +1185785869=barbassault_icons,0 +-607416949=prayeron,16 +-607416948=prayeron,17 +-1779684630=rune essence +-607416947=prayeron,18 +-607416946=prayeron,19 +1119460311=bandos battalion +-967559823=creature cruelty +-1904094243=zeah_fishing +-1913214770=wilderness +460367020=village +825919161=options_icons,39 +1585002375=magicon,18 +94839810=coins +1585002376=magicon,19 +825919160=options_icons,38 +-1282090556=faerie +-521895311=the adventurer +788399136=tree spirits +-1902858744=beneath_the_stronghold +-356730043=pirates of penance +1185785876=barbassault_icons,7 +825919158=options_icons,36 +825919159=options_icons,37 +825919156=options_icons,34 +-1455241861=victory is mine +825919157=options_icons,35 +-1333874720=side_icons,17 +825919154=options_icons,32 +825919155=options_icons,33 +825919152=options_icons,30 +825919153=options_icons,31 +1437805631=chatback +-1623296531=ground scape +685190118=in the brine +1366257555=nightfall +110327241=theme +-1333874725=side_icons,12 +-1333874726=side_icons,11 +-1333874727=side_icons,10 +-1333874721=side_icons,16 +-1333874722=side_icons,15 +-1124681475=darkly_altared +-8976533=throne of the demon +-1333874723=side_icons,14 +-1333874724=side_icons,13 +-1989106719=assault and battery +1958759012=greatness +1057075019=b12_full +1143353537=chain of command +-51091830=desert voyage +-1073927447=mirage +-91048728=number_button +3165239=gaol +-1380919269=breeze +445640248=rugged_terrain +106079=key +-655784411=overlay_multiway +-1025835715=backright2 +-1025835716=backright1 +1120933843=scape main +3225350=iban +-956253112=title fight +-123912401=la mort +1585002367=magicon,10 +-2128736428=startgame +1585002368=magicon,11 +-925031874=royale +1585002369=magicon,12 +1585002370=magicon,13 +1585002373=magicon,16 +1585002374=magicon,17 +-1307116191=superstition +1585002371=magicon,14 +1585002372=magicon,15 +-587569902=path of peril +3392903=null +-1601127242=inadequacy +2136330800=staticons2,0 +2136330801=staticons2,1 +344336468=grip of the talon +2136330804=staticons2,4 +2136330805=staticons2,5 +2136330802=staticons2,2 +2136330803=staticons2,3 +1960215130=barking mad +-43712789=scape original +621171714=cellar song +1585002461=magicon,41 +1585002462=magicon,42 +1585002460=magicon,40 +111485446=upass +2136330808=staticons2,8 +694847251=in the manor +2136330809=staticons2,9 +2136330806=staticons2,6 +2136330807=staticons2,7 +-1385847955=rightarrow +1343200077=the slayer +1585002465=magicon,45 +1585002466=magicon,46 +1585002463=magicon,43 +1585002464=magicon,44 +1585002469=magicon,49 +-95571520=volcanic vikings +1585002467=magicon,47 +1585002468=magicon,48 +-1032629963=shipwrecked +93330745=aztec +-881372797=tabs,1 +1377351472=oriental +121641580=headicons_hint +-881372798=tabs,0 +1585002438=magicon,39 +-1661605940=elfwood +-607416893=prayeron,30 +-822106577=jungle island xmas +-607416892=prayeron,31 +332368736=mad eadgar +1585002432=magicon,33 +-143368781=side_background_right +1585002433=magicon,34 +-1081494434=malady +1585002430=magicon,31 +1585002431=magicon,32 +1585002436=magicon,37 +1585002437=magicon,38 +1585002434=magicon,35 +1585002435=magicon,36 +-1938171360=miscgraphics3,0 +-1059680853=trinity +781557721=dies_irae +-1938171359=miscgraphics3,1 +497375231=stillness +-1938171358=miscgraphics3,2 +-1938171357=miscgraphics3,3 +-960709976=dogs of war +2129339089=magicon,1 +2129339088=magicon,0 +755433248=headicons_pk +108392383=regal +-213632750=waterfall +-1367706280=canvas +73828649=settlement +848123561=into the abyss +478781900=last stand +1339486127=the shadow +-1055503808=roc and roll +837204902=mapdots +950484242=compass +-1082154559=fanfare +747848680=nether_realm +788224888=dead quiet +1532279978=monarch waltz +-149029727=side_background_left1 +812947089=fanfare2 +-149029726=side_background_left2 +1006643748=high spirits +-2136059388=starlight +2122572442=the tower +-1998869913=spooky2 +1411067174=gnome village2 +-2065077267=wild side +812947090=fanfare3 +1585002429=magicon,30 +1294629755=on the wing +2097127567=monkey badness +-2032107216=sojourn +1020264019=pest control +3237038=info +1473393027=fe fi fo fum +-1686202291=upper_depths +3540994=stop +1742080803=darkwood +740392969=little cave of horrors +-158379532=prayerglow +-691855347=in between +-200702983=the noble rodent +1652745754=forgotten +-1895307673=hitmark,3 +-1895307674=hitmark,2 +-1895307675=hitmark,1 +-1895307676=hitmark,0 +-1895307670=hitmark,6 +-1895307671=hitmark,5 +-1895307672=hitmark,4 +1968917071=bone dry +-850506182=trawler minor +197029040=mapscene +-808772318=in the pits +-1165315580=looking back +-1763090403=scape_ape +-1938177931=miscgraphics,11 +-1938177932=miscgraphics,10 +1936130561=thrall_of_the_serpent +1258863383=yesteryear +1994744000=slice of silent movie +-1691854169=dead can dance +1585002407=magicon,29 +1585002405=magicon,27 +1585002406=magicon,28 +271319484=frostbite +-499867199=meridian +1585002400=magicon,22 +-1938177930=miscgraphics,12 +-84626226=mudskipper melody +1585002403=magicon,25 +1585002404=magicon,26 +3641802=wall +1585002401=magicon,23 +1585002402=magicon,24 +-606457701=wolf mountain +1276599785=button_brown +1969878899=emotes,13 +1969878897=emotes,11 +1969878898=emotes,12 +1969878896=emotes,10 +-1938177928=miscgraphics,14 +-907669678=brew hoo hoo +-1938177929=miscgraphics,13 +72999866=subterranea +619237947=the galleon +-1764950404=scape sad +295831445=heart and mind +908430134=dangerous road +738888631=tradebacking +-174800339=verdana_11pt_regular +686705631=lightwalk +-601591436=side_background_bottom +-1479412376=the navigator +-359173459=zamorak zoo +744536246=null and void +-1701556798=magicoff2,39 +-1701556799=magicoff2,38 +-1396384012=bamboo +-200388662=lighthouse +133626717=suspicious +-810515425=voyage +3061973=crag +1802291895=big chords +-1661619479=elfwall +113315621=wood2 +1813041183=steelborder2,0 +429244831=slug a bug ball +1813041184=steelborder2,1 +-1658386264=shining_spirit +738909086=chamber +526264239=verdana_13pt_regular +-877351859=temple +2142215577=the mollusc menace +1124498189=warpath +-2136649922=no way out +-339706871=grimly_fiendish +547534551=wrath and ruin +544229147=lore and order +3327206=load +1610073470=lovakengj_sigil +-419218284=long way home +-662489856=food for thought +1306461568=stagnant +-1662171955=elfdoor +-1043985601=meddling kids +947464074=titlebutton +-1309055712=exposed +-1487348923=ambient jungle +-1829469821=lament of meiyerditch +233203434=leftarrow_small +-1216167350=dangerous +114464611=railings +-1106574323=legend +-1701556767=magicoff2,49 +-999707515=time to mine +-1701556768=magicoff2,48 +2129339097=magicon,9 +2129339096=magicon,8 +1959211510=mapfunction,42 +2129339095=magicon,7 +1959211511=mapfunction,43 +2129339094=magicon,6 +2129339093=magicon,5 +2129339092=magicon,4 +2129339091=magicon,3 +2129339090=magicon,2 +3522472=saga +-1701556769=magicoff2,47 +-544722449=rellekka +1033441676=tribal background +1915718129=the desolate isle +1890607150=magicon2,19 +1787618597=stranded +1717999087=forgettable melody +1959211512=mapfunction,44 +-243680400=peng_emotes,0 +1959211513=mapfunction,45 +1959211514=mapfunction,46 +1959211515=mapfunction,47 +1959211516=mapfunction,48 +1959211517=mapfunction,49 +1705947058=the cellar dwellers +1216634785=landlubber +1884768198=magicoff,40 +-1588113323=the rogues den +1884768199=magicoff,41 +-905842564=serene +-607599698=prayeroff,2 +-607599699=prayeroff,1 +1890607142=magicon2,11 +1389384362=monkey trouble +1890607143=magicon2,12 +1890607144=magicon2,13 +1890607145=magicon2,14 +1890607146=magicon2,15 +1890607147=magicon2,16 +1890607148=magicon2,17 +1966766798=mausoleum +1890607149=magicon2,18 +1808345541=armadyl alliance +290391725=options_slider,7 +1890607141=magicon2,10 +290391722=options_slider,4 +-607599696=prayeroff,4 +290391721=options_slider,3 +-607599697=prayeroff,3 +290391724=options_slider,6 +-607599694=prayeroff,6 +290391723=options_slider,5 +-607599695=prayeroff,5 +-607599692=prayeroff,8 +-607599693=prayeroff,7 +290391720=options_slider,2 +-607599691=prayeroff,9 +1334775925=chat_background +-1779127378=arabian2 +-1779127377=arabian3 +528722471=island life +1890607175=magicon2,23 +1890607176=magicon2,24 +949634504=mouse trap +1890607177=magicon2,25 +1890607178=magicon2,26 +1890607179=magicon2,27 +3327403=logo +290391719=options_slider,1 +1092249049=storm brew +290391718=options_slider,0 +404357804=everywhere +1890607172=magicon2,20 +1890607173=magicon2,21 +951530772=contest +1890607174=magicon2,22 +-1701556776=magicoff2,40 +1884768176=magicoff,39 +-395250469=corporal punishment +1749113330=newbie melody +1884768174=magicoff,37 +1884768175=magicoff,38 +-1701556772=magicoff2,44 +-1701556773=magicoff2,43 +-1701556774=magicoff2,42 +-1701556775=magicoff2,41 +-858121616=tzhaar +1884768172=magicoff,35 +1884768173=magicoff,36 +666772244=combat_shield +1884768170=magicoff,33 +1884768171=magicoff,34 +1959211509=mapfunction,41 +1639695510=mapmarker +-1661748240=friends_icons +-552301350=knightly +-1918044851=mastermindless +-1701556770=magicoff2,46 +-1701556771=magicoff2,45 +201526300=corporealbeast +1959211508=mapfunction,40 +105001967=nomad +-70910145=clickcross,3 +-1737914947=mapfunction,5 +1801745440=staticons2,11 +-70910146=clickcross,2 +-865479038=tribal +-1737914946=mapfunction,6 +1801745441=staticons2,12 +-70910147=clickcross,1 +-1737914945=mapfunction,7 +1801745442=staticons2,13 +-70910148=clickcross,0 +-1737914944=mapfunction,8 +1801745443=staticons2,14 +-1737914943=mapfunction,9 +1801745444=staticons2,15 +1801745445=staticons2,16 +1801745446=staticons2,17 +-1877545169=land down under +93921962=books +-1737914949=mapfunction,3 +-1737914948=mapfunction,4 +-1655721428=prayeroff,19 +-2136884405=title.jpg +-1655721429=prayeroff,18 +1584819628=magicoff,6 +1584819629=magicoff,7 +437480876=voodoo cult +1584819624=magicoff,2 +1584819625=magicoff,3 +1584819626=magicoff,4 +-1737914952=mapfunction,0 +1584819627=magicoff,5 +-1737914951=mapfunction,1 +124995564=harmony2 +1584819622=magicoff,0 +1801745439=staticons2,10 +1584819623=magicoff,1 +346263512=dorgeshun city +-1665011705=down and out +1890607180=magicon2,28 +1890607181=magicon2,29 +1417471781=titlescroll +1959211446=mapfunction,20 +1956141536=options_radio_buttons,0 +736568812=ballad of enchantment +-1737914950=mapfunction,2 +1959211447=mapfunction,21 +1959211448=mapfunction,22 +1959211449=mapfunction,23 +-1890130256=morytania +-70910141=clickcross,7 +-70910142=clickcross,6 +1956141539=options_radio_buttons,3 +-70910143=clickcross,5 +1956141538=options_radio_buttons,2 +284766976=splendour +-70910144=clickcross,4 +1956141537=options_radio_buttons,1 +196677638=the quizmaster +530068296=overture +-1123094568=sl_button +-700552779=hosidius_sigil +-614076819=sad meadow +1956141543=options_radio_buttons,7 +1956141542=options_radio_buttons,6 +1956141541=options_radio_buttons,5 +1956141540=options_radio_buttons,4 +1584819631=magicoff,9 +1846633612=gnome village +-2128560371=sl_back +1969878905=emotes,19 +306819362=crystal castle +1584819630=magicoff,8 +1969878903=emotes,17 +303737220=options_icons,7 +1969878904=emotes,18 +1969878901=emotes,15 +-78220817=devils_may_care +1969878902=emotes,16 +-40521666=dimension x +1969878900=emotes,14 +673424924=the lunar isle +789609582=brimstail's scales +303737222=options_icons,9 +303737221=options_icons,8 +1959211415=mapfunction,10 +3059343=coil +1959211416=mapfunction,11 +1959211417=mapfunction,12 +-1256560486=last_man_standing +1959211418=mapfunction,13 +336238005=rightarrow_small +1959211419=mapfunction,14 +-677662361=forever +-1655721430=prayeroff,17 +-1655721431=prayeroff,16 +-1655721432=prayeroff,15 +1959211420=mapfunction,15 +1959211663=mapfunction,90 +1959211421=mapfunction,16 +1959211422=mapfunction,17 +1959211423=mapfunction,18 +303737217=options_icons,4 +303737216=options_icons,3 +303737215=options_icons,2 +-1665005042=funny bunnies +303737214=options_icons,1 +303737219=options_icons,6 +303737218=options_icons,5 +303737213=options_icons,0 +95997798=we are the fairies +2001751835=desert heat +1959211424=mapfunction,19 +-1655721437=prayeroff,10 +687938017=clanwars +-1776024210=desolate_mage +-650944128=strength of saradomin +-1655721433=prayeroff,14 +1160873524=aye car rum ba +-1655721434=prayeroff,13 +-1655721435=prayeroff,12 +-1081314499=marble +-1655721436=prayeroff,11 +1097075475=reset,0 +1959211477=mapfunction,30 +-693313916=warriors guild +3506388=roof +1959211478=mapfunction,31 +1097075476=reset,1 +-2134967800=dagannoth dawn +-985763247=planks +1959211479=mapfunction,32 +1999746381=fenkenstrain's refrain +898010371=garden of winter +359174830=rat hunt +1959211482=mapfunction,35 +686441581=lightness +1959211483=mapfunction,36 +1959211484=mapfunction,37 +1959211485=mapfunction,38 +1959211486=mapfunction,39 +2023201035=dwarf theme +1959211480=mapfunction,33 +1959211481=mapfunction,34 +-1065532022=combatboxes,1 +-1065532021=combatboxes,2 +-1065532020=combatboxes,3 +1867160429=old_tiles +394756979=scape santa +25205919=elfroof2 +-663428071=dangerous way +-1065532023=combatboxes,0 +1959211450=mapfunction,24 +-895939599=spirit +1959211451=mapfunction,25 +1959211452=mapfunction,26 +1959211453=mapfunction,27 +1959211454=mapfunction,28 +1959211455=mapfunction,29 +-275310687=undercurrent +212205923=goblin village +-303898981=faithless +-1381531001=tomb raider +260940912=marzipan +1343267530=backhmid1 +1343267531=backhmid2 +1097468315=horizon +-1655721404=prayeroff,22 +623451622=kourend_the_magnificent +-1655721405=prayeroff,21 +-1655721406=prayeroff,20 +-1655721400=prayeroff,26 +-313384067=p12_full +-1655721401=prayeroff,25 +-1655721402=prayeroff,24 +-1655721403=prayeroff,23 +95848451=dream +1966781751=maws_jaws_claws +-995428255=parade +95734525=method of madness +-1308064877=hitmarks +1030045177=mutant medley +1333034828=blackmark +851641665=davy jones locker +417793574=scrollbar +1346720900=backleft2 +1884768206=magicoff,48 +1884768207=magicoff,49 +1801140808=fangs for the memory +1884768204=magicoff,46 +1345432055=pinball wizard +1884768205=magicoff,47 +-783693496=dance of the undead +1274780903=chompy hunt +465278529=the lost tribe +-1666437481=combaticons3,6 +2032696205=cabin fever +-1666437480=combaticons3,7 +1825640471=borderland +415928477=zeah_mining +1884768202=magicoff,44 +-607599700=prayeroff,0 +1884768203=magicoff,45 +1884768200=magicoff,42 +1884768201=magicoff,43 +-1666437487=combaticons3,0 +-1666437486=combaticons3,1 +813726263=crystal cave +1235442953=pathways +518814479=lullaby +-1666437483=combaticons3,4 +1585007985=magicon2,7 +-1666437482=combaticons3,5 +1585007986=magicon2,8 +-1666437485=combaticons3,2 +1585007987=magicon2,9 +-1666437484=combaticons3,3 +104080482=moody +1969878967=emotes,39 +-665666447=work work work +1364992651=evil bobs island +1969878965=emotes,37 +1227328817=verdana_15pt_regular +1969878966=emotes,38 +1969878963=emotes,35 +1581724013=monkey business +1969878964=emotes,36 +-74307138=miscellania +1969878961=emotes,33 +1969878962=emotes,34 +1969878960=emotes,32 +1131171307=wayward +-1154558416=sl_arrows +529929957=overpass +1258058669=huffman +-694094064=adventure +1171698653=orb_xp,1 +1171698652=orb_xp,0 +1171698655=orb_xp,3 +1171698654=orb_xp,2 +1802171733=arceuus_sigil +221109227=tears of guthix +-1661754893=elfroof +-1666437478=combaticons3,9 +1124565314=warrior +1969878958=emotes,30 +1969878959=emotes,31 +-934797897=reggae +2110260221=the genie +-1666437479=combaticons3,8 +104257585=mummy +-1923924724=sworddecor,0 +2136325196=staticons,17 +-1332194002=background +2136164423=homescape +-1923924721=sworddecor,3 +2136325194=staticons,15 +-1923924722=sworddecor,2 +-873564465=tiptoe +2136325195=staticons,16 +-1923924723=sworddecor,1 +123560953=espionage +2136325192=staticons,13 +2136325193=staticons,14 +1038911415=gnome king +2136325190=staticons,11 +2136325191=staticons,12 +1854274741=karamja jam +1969878989=emotes,40 +-1242708793=glyphs +563269755=the terrible tower +1650323088=twilight +-12868552=sea shanty +289742397=book of spells +1880989696=dragontooth island +2136325189=staticons,10 +110873=pen +-454421102=out of the deep +825919099=options_icons,19 +825919097=options_icons,17 +825919098=options_icons,18 +-1349119470=cursed +1585007978=magicon2,0 +825919095=options_icons,15 +1585007979=magicon2,1 +825919096=options_icons,16 +825919093=options_icons,13 +825919094=options_icons,14 +825919091=options_icons,11 +825919092=options_icons,12 +-895977880=sphinx +-874529881=city of the dead +825919090=options_icons,10 +1585007981=magicon2,3 +1585007982=magicon2,4 +1585007983=magicon2,5 +-1618729246=body parts +1585007984=magicon2,6 +1585007980=magicon2,2 +1086075866=shayzien_sigil +2103661451=jester minute +-911346307=steelborder,2 +-1809781334=button_brown_big +-911346306=steelborder,3 +-816227352=vision +-911346309=steelborder,0 +109407595=shine +-911346308=steelborder,1 +-119954464=combaticons3,12 +-119954465=combaticons3,11 +-1846853118=armageddon +-119954462=combaticons3,14 +-119954463=combaticons3,13 +-119954460=combaticons3,16 +-908183966=scarab +-119954461=combaticons3,15 +-282886672=home sweet home +103666243=march +1969878929=emotes,22 +1643875326=fire and brimstone +1969878927=emotes,20 +-119954466=combaticons3,10 +1969878928=emotes,21 +-839455633=close quarters +941457503=way of the enchanter +-1453405761=mor-ul-rek +-415134015=have a blast +-1658514874=floating free +1213477442=chickened out +-1619800349=emotes_locked,27 +-599680631=fear and loathing +-1081041422=insect queen +-1268786147=forest +-119954459=combaticons3,17 +-119954457=combaticons3,19 +-119954458=combaticons3,18 +-850395529=trouble brewing +-1773920521=cave of the goblins +-771284962=claustrophobia +1310729739=bankbuttons,2 +1306691868=upcoming +1465443077=over to nardah +1310729744=bankbuttons,7 +1310729743=bankbuttons,6 +1310729742=bankbuttons,5 +1234827707=deep wildy +-90350772=xenophobe +-750127868=arabian +1310729741=bankbuttons,4 +1310729740=bankbuttons,3 +1041911129=waterlogged +108875897=runes +1447063382=barb wire +-378865792=magic dance +285466503=overlay_duel +1814287296=zeah_magic +1976894499=down to earth +1969878936=emotes,29 +1969878934=emotes,27 +-1567437308=deadlands +1969878935=emotes,28 +1310729738=bankbuttons,1 +1969878932=emotes,25 +-957019274=too many cooks +1310729737=bankbuttons,0 +2092627105=silence +1969878933=emotes,26 +1969878930=emotes,23 +1969878931=emotes,24 +-901674570=well of voyage diff --git a/cache/src/test/java/net/runelite/cache/AreaDumper.java b/cache/src/test/java/net/runelite/cache/AreaDumper.java index 0b276fcce1..2aab1db48b 100644 --- a/cache/src/test/java/net/runelite/cache/AreaDumper.java +++ b/cache/src/test/java/net/runelite/cache/AreaDumper.java @@ -64,7 +64,7 @@ public class AreaDumper for (AreaDefinition area : areaManager.getAreas()) { - Files.asCharSink(new File(outDir, area.id + ".json"), Charset.defaultCharset()).write(gson.toJson(area)); + Files.asCharSink(new File(outDir, area.id + ".json"), Charset.defaultCharset()).write(gson.toJson(area)); ++count; } } diff --git a/cache/src/test/java/net/runelite/cache/SoundEffectsDumperTest.java b/cache/src/test/java/net/runelite/cache/SoundEffectsDumperTest.java index 5ac0c9e1c5..a389e86139 100644 --- a/cache/src/test/java/net/runelite/cache/SoundEffectsDumperTest.java +++ b/cache/src/test/java/net/runelite/cache/SoundEffectsDumperTest.java @@ -24,14 +24,20 @@ */ package net.runelite.cache; +import com.google.common.io.FileWriteMode; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import java.io.File; + + import java.io.IOException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.nio.charset.Charset; -import net.runelite.cache.definitions.loaders.sound.SoundEffectLoader; -import net.runelite.cache.definitions.sound.SoundEffectDefinition; +import net.runelite.cache.definitions.loaders.sound.SoundEffectTrackLoader; +import net.runelite.cache.definitions.sound.SoundEffectTrackDefinition; import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.Index; import net.runelite.cache.fs.Storage; @@ -42,6 +48,11 @@ import org.junit.rules.TemporaryFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + public class SoundEffectsDumperTest { private static final Logger logger = LoggerFactory.getLogger(SoundEffectsDumperTest.class); @@ -68,8 +79,8 @@ public class SoundEffectsDumperTest { byte[] contents = archive.decompress(storage.loadArchive(archive)); - SoundEffectLoader soundEffectLoader = new SoundEffectLoader(); - SoundEffectDefinition soundEffect = soundEffectLoader.load(contents); + SoundEffectTrackLoader setLoader = new SoundEffectTrackLoader(); + SoundEffectTrackDefinition soundEffect = setLoader.load(contents); Files.asCharSink(new File(dumpDir, archive.getArchiveId() + ".json"), Charset.defaultCharset()).write(gson.toJson(soundEffect)); ++count; @@ -78,4 +89,58 @@ public class SoundEffectsDumperTest logger.info("Dumped {} sound effects to {}", count, dumpDir); } -} + + @Test + public void extractWavTest() throws IOException + { + File dumpDir = folder.newFolder(); + int count = 0; + + try (Store store = new Store(StoreLocation.LOCATION)) + { + store.load(); + + Storage storage = store.getStorage(); + Index index = store.getIndex(IndexType.SOUNDEFFECTS); + + for (Archive archive : index.getArchives()) + { + byte[] contents = archive.decompress(storage.loadArchive(archive)); + + SoundEffectTrackLoader setLoader = new SoundEffectTrackLoader(); + SoundEffectTrackDefinition soundEffect = setLoader.load(contents); + try + { + Object audioStream; + byte[] data = soundEffect.mix(); + + AudioFormat audioFormat = new AudioFormat(22050.0f, 8, 1, true, false); + audioStream = new AudioInputStream(new ByteArrayInputStream(data), audioFormat, data.length); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + AudioSystem.write((AudioInputStream) audioStream, AudioFileFormat.Type.WAVE, bos); + data = bos.toByteArray(); + + FileOutputStream fos = new FileOutputStream(new File(dumpDir, archive.getArchiveId() + ".wav")); + + try + { + fos.write(data); + } + finally + { + fos.close(); + } + + ++count; + } + catch (Exception e) + { + continue; + } + } + } + + logger.info("Dumped {} sound effects to {}", count, dumpDir); + } +} \ No newline at end of file diff --git a/cache/src/test/java/net/runelite/cache/TitleDumper.java b/cache/src/test/java/net/runelite/cache/TitleDumper.java index c611ddf04a..e6cbe138d2 100644 --- a/cache/src/test/java/net/runelite/cache/TitleDumper.java +++ b/cache/src/test/java/net/runelite/cache/TitleDumper.java @@ -47,8 +47,8 @@ public class TitleDumper @Test public void extract() throws IOException { - File base = StoreLocation.LOCATION, - outFile = folder.newFolder(); + File base = StoreLocation.LOCATION; + File outFile = folder.newFile(); try (Store store = new Store(base)) { diff --git a/cache/src/test/java/net/runelite/cache/definitions/savers/InterfaceSaverTest.java b/cache/src/test/java/net/runelite/cache/definitions/savers/InterfaceSaverTest.java index 6d95f445a9..e1955a6f79 100644 --- a/cache/src/test/java/net/runelite/cache/definitions/savers/InterfaceSaverTest.java +++ b/cache/src/test/java/net/runelite/cache/definitions/savers/InterfaceSaverTest.java @@ -1,65 +1,65 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.definitions.savers; - -import java.io.File; -import net.runelite.cache.IndexType; -import net.runelite.cache.StoreLocation; -import net.runelite.cache.definitions.InterfaceDefinition; -import net.runelite.cache.definitions.loaders.InterfaceLoader; -import net.runelite.cache.fs.Archive; -import net.runelite.cache.fs.ArchiveFiles; -import net.runelite.cache.fs.FSFile; -import net.runelite.cache.fs.Index; -import net.runelite.cache.fs.Storage; -import net.runelite.cache.fs.Store; -import static org.junit.Assert.assertArrayEquals; -import org.junit.Test; - -public class InterfaceSaverTest -{ - @Test - public void testSave() throws Exception - { - File base = StoreLocation.LOCATION; - try (Store store = new Store(base)) - { - store.load(); - - Storage storage = store.getStorage(); - Index index = store.getIndex(IndexType.INTERFACES); - Archive archive = index.getArchive(31); - byte[] archiveData = storage.loadArchive(archive); - ArchiveFiles files = archive.getFiles(archiveData); - FSFile file = files.findFile(76); - byte[] contents = file.getContents(); - - InterfaceDefinition def = new InterfaceLoader().load(0, contents); - byte[] b = new InterfaceSaver().save(def); - assertArrayEquals(contents, b); - } - } - -} +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.savers; + +import java.io.File; +import net.runelite.cache.IndexType; +import net.runelite.cache.StoreLocation; +import net.runelite.cache.definitions.InterfaceDefinition; +import net.runelite.cache.definitions.loaders.InterfaceLoader; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.ArchiveFiles; +import net.runelite.cache.fs.FSFile; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Storage; +import net.runelite.cache.fs.Store; +import static org.junit.Assert.assertArrayEquals; +import org.junit.Test; + +public class InterfaceSaverTest +{ + @Test + public void testSave() throws Exception + { + File base = StoreLocation.LOCATION; + try (Store store = new Store(base)) + { + store.load(); + + Storage storage = store.getStorage(); + Index index = store.getIndex(IndexType.INTERFACES); + Archive archive = index.getArchive(31); + byte[] archiveData = storage.loadArchive(archive); + ArchiveFiles files = archive.getFiles(archiveData); + FSFile file = files.findFile(76); + byte[] contents = file.getContents(); + + InterfaceDefinition def = new InterfaceLoader().load(0, contents); + byte[] b = new InterfaceSaver().save(def); + assertArrayEquals(contents, b); + } + } + +} diff --git a/cache/src/test/java/net/runelite/cache/definitions/savers/ScriptSaverTest.java b/cache/src/test/java/net/runelite/cache/definitions/savers/ScriptSaverTest.java index a2b55676bd..4d429632ca 100644 --- a/cache/src/test/java/net/runelite/cache/definitions/savers/ScriptSaverTest.java +++ b/cache/src/test/java/net/runelite/cache/definitions/savers/ScriptSaverTest.java @@ -1,66 +1,66 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.definitions.savers; - -import java.io.IOException; -import net.runelite.cache.definitions.ScriptDefinition; -import net.runelite.cache.definitions.loaders.ScriptLoader; -import net.runelite.cache.script.Instructions; -import net.runelite.cache.script.assembler.Assembler; -import static org.junit.Assert.assertEquals; -import org.junit.Test; - -/** - * - * @author Adam - */ -public class ScriptSaverTest -{ - private static final String SCRIPT_RESOURCE = "/net/runelite/cache/script/assembler/91.rs2asm"; - private static final String SCRIPT_RESOURCE_UNICODE = "/net/runelite/cache/script/assembler/Unicode.rs2asm"; - - @Test - public void testSave() throws IOException - { - Instructions instructions = new Instructions(); - instructions.init(); - ScriptDefinition script = new Assembler(instructions).assemble(getClass().getResourceAsStream(SCRIPT_RESOURCE)); - byte[] saved = new ScriptSaver().save(script); - ScriptDefinition loadedScripot = new ScriptLoader().load(91, saved); - assertEquals(script, loadedScripot); - } - - @Test - public void testSaveUnicode() throws IOException - { - Instructions instructions = new Instructions(); - instructions.init(); - ScriptDefinition script = new Assembler(instructions).assemble(getClass().getResourceAsStream(SCRIPT_RESOURCE_UNICODE)); - byte[] saved = new ScriptSaver().save(script); - ScriptDefinition loadedScripot = new ScriptLoader().load(1001, saved); - assertEquals(script, loadedScripot); - } - -} +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.definitions.savers; + +import java.io.IOException; +import net.runelite.cache.definitions.ScriptDefinition; +import net.runelite.cache.definitions.loaders.ScriptLoader; +import net.runelite.cache.script.Instructions; +import net.runelite.cache.script.assembler.Assembler; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * + * @author Adam + */ +public class ScriptSaverTest +{ + private static final String SCRIPT_RESOURCE = "/net/runelite/cache/script/assembler/91.rs2asm"; + private static final String SCRIPT_RESOURCE_UNICODE = "/net/runelite/cache/script/assembler/Unicode.rs2asm"; + + @Test + public void testSave() throws IOException + { + Instructions instructions = new Instructions(); + instructions.init(); + ScriptDefinition script = new Assembler(instructions).assemble(getClass().getResourceAsStream(SCRIPT_RESOURCE)); + byte[] saved = new ScriptSaver().save(script); + ScriptDefinition loadedScripot = new ScriptLoader().load(91, saved); + assertEquals(script, loadedScripot); + } + + @Test + public void testSaveUnicode() throws IOException + { + Instructions instructions = new Instructions(); + instructions.init(); + ScriptDefinition script = new Assembler(instructions).assemble(getClass().getResourceAsStream(SCRIPT_RESOURCE_UNICODE)); + byte[] saved = new ScriptSaver().save(script); + ScriptDefinition loadedScripot = new ScriptLoader().load(1001, saved); + assertEquals(script, loadedScripot); + } + +} diff --git a/cache/src/test/java/net/runelite/cache/fs/ContainerTest.java b/cache/src/test/java/net/runelite/cache/fs/ContainerTest.java index 1aca0df4ba..113385909b 100644 --- a/cache/src/test/java/net/runelite/cache/fs/ContainerTest.java +++ b/cache/src/test/java/net/runelite/cache/fs/ContainerTest.java @@ -1,55 +1,55 @@ -/* - * Copyright (c) 2016-2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.fs; - -import java.io.IOException; -import java.util.Random; -import static net.runelite.cache.fs.jagex.CompressionType.GZ; -import static org.junit.Assert.assertArrayEquals; -import org.junit.Test; - -public class ContainerTest -{ - - @Test - public void testCompress() throws IOException - { - int[] keys = new int[] - { - 4, 8, 15, 16 - }; - Random random = new Random(42L); - byte[] data = new byte[1024]; - random.nextBytes(data); - - Container container = new Container(GZ, -1); - container.compress(data, keys); - byte[] compressedData = container.data; - - container = Container.decompress(compressedData, keys); - assertArrayEquals(data, container.data); - } - -} +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.fs; + +import java.io.IOException; +import java.util.Random; +import static net.runelite.cache.fs.jagex.CompressionType.GZ; +import static org.junit.Assert.assertArrayEquals; +import org.junit.Test; + +public class ContainerTest +{ + + @Test + public void testCompress() throws IOException + { + int[] keys = new int[] + { + 4, 8, 15, 16 + }; + Random random = new Random(42L); + byte[] data = new byte[1024]; + random.nextBytes(data); + + Container container = new Container(GZ, -1); + container.compress(data, keys); + byte[] compressedData = container.data; + + container = Container.decompress(compressedData, keys); + assertArrayEquals(data, container.data); + } + +} diff --git a/cache/src/test/java/net/runelite/cache/fs/jagex/DiskStorageTest.java b/cache/src/test/java/net/runelite/cache/fs/jagex/DiskStorageTest.java index 6d6035a61e..240b7c67d7 100644 --- a/cache/src/test/java/net/runelite/cache/fs/jagex/DiskStorageTest.java +++ b/cache/src/test/java/net/runelite/cache/fs/jagex/DiskStorageTest.java @@ -1,102 +1,102 @@ -/* - * Copyright (c) 2016-2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.fs.jagex; - -import java.io.File; -import net.runelite.cache.StoreLocation; -import net.runelite.cache.fs.Archive; -import net.runelite.cache.fs.Container; -import net.runelite.cache.fs.Index; -import net.runelite.cache.fs.Store; -import net.runelite.cache.index.FileData; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; - -public class DiskStorageTest -{ - @Rule - public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); - - @Test - public void testSaveArchive() throws Exception - { - File file = folder.newFolder(); - DiskStorage storage = new DiskStorage(file); - Archive archive; - Archive archive2; - try (Store store = new Store(storage)) - { - Index index = store.addIndex(0); - archive = index.addArchive(0); - archive2 = index.addArchive(1); - - FileData[] fileData = new FileData[1]; - archive.setFileData(fileData); - fileData[0] = new FileData(); - - FileData[] fileData2 = new FileData[1]; - archive2.setFileData(fileData2); - fileData2[0] = new FileData(); - - byte[] data = "test".getBytes(); - Container container = new Container(archive.getCompression(), -1); - container.compress(data, null); - byte[] compressedData = container.data; - storage.saveArchive(archive, compressedData); - - container = new Container(archive.getCompression(), 42); - container.compress(data, null); - compressedData = container.data; - archive2.setRevision(42); - storage.saveArchive(archive2, compressedData); - - store.save(); - } - - storage = new DiskStorage(file); - try (Store store = new Store(storage)) - { - store.load(); - Index index = store.findIndex(0); - Archive archive2_1 = index.getArchive(0); - Archive archive2_2 = index.getArchive(1); - - byte[] comprsesedData = storage.loadArchive(archive2_1); - byte[] data = archive2_1.decompress(comprsesedData); - assertArrayEquals("test".getBytes(), data); - assertEquals(archive.getCrc(), archive2_1.getCrc()); - assertEquals(archive.getRevision(), archive2_1.getRevision()); - - comprsesedData = storage.loadArchive(archive2_2); - data = archive2_2.decompress(comprsesedData); - assertArrayEquals("test".getBytes(), data); - assertEquals(archive2.getCrc(), archive2_2.getCrc()); - assertEquals(archive2.getRevision(), archive2_2.getRevision()); - } - } - -} +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.fs.jagex; + +import java.io.File; +import net.runelite.cache.StoreLocation; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.Container; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Store; +import net.runelite.cache.index.FileData; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +public class DiskStorageTest +{ + @Rule + public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); + + @Test + public void testSaveArchive() throws Exception + { + File file = folder.newFolder(); + DiskStorage storage = new DiskStorage(file); + Archive archive; + Archive archive2; + try (Store store = new Store(storage)) + { + Index index = store.addIndex(0); + archive = index.addArchive(0); + archive2 = index.addArchive(1); + + FileData[] fileData = new FileData[1]; + archive.setFileData(fileData); + fileData[0] = new FileData(); + + FileData[] fileData2 = new FileData[1]; + archive2.setFileData(fileData2); + fileData2[0] = new FileData(); + + byte[] data = "test".getBytes(); + Container container = new Container(archive.getCompression(), -1); + container.compress(data, null); + byte[] compressedData = container.data; + storage.saveArchive(archive, compressedData); + + container = new Container(archive.getCompression(), 42); + container.compress(data, null); + compressedData = container.data; + archive2.setRevision(42); + storage.saveArchive(archive2, compressedData); + + store.save(); + } + + storage = new DiskStorage(file); + try (Store store = new Store(storage)) + { + store.load(); + Index index = store.findIndex(0); + Archive archive2_1 = index.getArchive(0); + Archive archive2_2 = index.getArchive(1); + + byte[] comprsesedData = storage.loadArchive(archive2_1); + byte[] data = archive2_1.decompress(comprsesedData); + assertArrayEquals("test".getBytes(), data); + assertEquals(archive.getCrc(), archive2_1.getCrc()); + assertEquals(archive.getRevision(), archive2_1.getRevision()); + + comprsesedData = storage.loadArchive(archive2_2); + data = archive2_2.decompress(comprsesedData); + assertArrayEquals("test".getBytes(), data); + assertEquals(archive2.getCrc(), archive2_2.getCrc()); + assertEquals(archive2.getRevision(), archive2_2.getRevision()); + } + } + +} diff --git a/cache/src/test/java/net/runelite/cache/io/OutputStreamTest.java b/cache/src/test/java/net/runelite/cache/io/OutputStreamTest.java index 42bbc238bb..8fd46d9137 100644 --- a/cache/src/test/java/net/runelite/cache/io/OutputStreamTest.java +++ b/cache/src/test/java/net/runelite/cache/io/OutputStreamTest.java @@ -1,59 +1,59 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.io; - -import static org.junit.Assert.assertEquals; -import org.junit.Test; - -public class OutputStreamTest -{ - @Test - public void testWriteBigSmart() - { - OutputStream os = new OutputStream(); - os.writeBigSmart(42); - os.writeBigSmart(70000); - os.writeBigSmart(65535); - - InputStream is = new InputStream(os.getArray()); - assertEquals(42, is.readBigSmart()); - assertEquals(70000, is.readBigSmart()); - assertEquals(65535, is.readBigSmart()); - } - - @Test - public void testWriteString() - { - char[] c = new char[]{32, 160}; - String str = new String(c, 0, c.length); - - OutputStream os = new OutputStream(); - os.writeString(str); - - // 1 byte length + 32 + 160 - assertEquals(3, os.getOffset()); - } - -} +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.io; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class OutputStreamTest +{ + @Test + public void testWriteBigSmart() + { + OutputStream os = new OutputStream(); + os.writeBigSmart(42); + os.writeBigSmart(70000); + os.writeBigSmart(65535); + + InputStream is = new InputStream(os.getArray()); + assertEquals(42, is.readBigSmart()); + assertEquals(70000, is.readBigSmart()); + assertEquals(65535, is.readBigSmart()); + } + + @Test + public void testWriteString() + { + char[] c = new char[]{32, 160}; + String str = new String(c, 0, c.length); + + OutputStream os = new OutputStream(); + os.writeString(str); + + // 1 byte length + 32 + 160 + assertEquals(3, os.getOffset()); + } + +} diff --git a/cache/src/test/java/net/runelite/cache/item/ItemSpriteFactoryTest.java b/cache/src/test/java/net/runelite/cache/item/ItemSpriteFactoryTest.java deleted file mode 100644 index 30fb140a1b..0000000000 --- a/cache/src/test/java/net/runelite/cache/item/ItemSpriteFactoryTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2018, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.item; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import javax.imageio.ImageIO; -import lombok.extern.slf4j.Slf4j; -import net.runelite.cache.IndexType; -import net.runelite.cache.ItemManager; -import net.runelite.cache.SpriteManager; -import net.runelite.cache.StoreLocation; -import net.runelite.cache.TextureManager; -import net.runelite.cache.definitions.ItemDefinition; -import net.runelite.cache.definitions.ModelDefinition; -import net.runelite.cache.definitions.loaders.ModelLoader; -import net.runelite.cache.definitions.providers.ModelProvider; -import net.runelite.cache.fs.Archive; -import net.runelite.cache.fs.Index; -import net.runelite.cache.fs.Store; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -@Slf4j -public class ItemSpriteFactoryTest -{ - @Rule - public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); - - @Test - @Ignore - public void test() throws IOException - { - File base = StoreLocation.LOCATION, - outDir = folder.newFolder(); - - int count = 0; - - try (Store store = new Store(base)) - { - store.load(); - - ItemManager itemManager = new ItemManager(store); - itemManager.load(); - - ModelProvider modelProvider = new ModelProvider() - { - @Override - public ModelDefinition provide(int modelId) throws IOException - { - Index models = store.getIndex(IndexType.MODELS); - Archive archive = models.getArchive(modelId); - - byte[] data = archive.decompress(store.getStorage().loadArchive(archive)); - ModelDefinition inventoryModel = new ModelLoader().load(modelId, data); - return inventoryModel; - } - }; - - SpriteManager spriteManager = new SpriteManager(store); - spriteManager.load(); - - TextureManager textureManager = new TextureManager(store); - textureManager.load(); - - for (ItemDefinition itemDef : itemManager.getItems()) - { - if (itemDef.name == null || itemDef.name.equalsIgnoreCase("null")) - { - continue; - } - - try - { - BufferedImage sprite = ItemSpriteFactory.createSprite(itemManager, modelProvider, spriteManager, textureManager, - itemDef.id, 1, 1, 3153952, false); - - File out = new File(outDir, itemDef.id + ".png"); - BufferedImage img = sprite; - ImageIO.write(img, "PNG", out); - - ++count; - } - catch (Exception ex) - { - log.warn("error dumping item {}", itemDef.id, ex); - } - } - } - - log.info("Dumped {} item images to {}", count, outDir); - } -} \ No newline at end of file diff --git a/cache/src/test/java/net/runelite/cache/util/Djb2Test.java b/cache/src/test/java/net/runelite/cache/util/Djb2Test.java index 5c3933b69d..49fb44df11 100644 --- a/cache/src/test/java/net/runelite/cache/util/Djb2Test.java +++ b/cache/src/test/java/net/runelite/cache/util/Djb2Test.java @@ -1,40 +1,40 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.util; - -import org.junit.Test; -import static org.junit.Assert.*; - -public class Djb2Test -{ - - @Test - public void testHash() - { - int hash = Djb2.hash("l49_52"); - assertEquals(-1153204821, hash); - } - -} +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.util; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class Djb2Test +{ + + @Test + public void testHash() + { + int hash = Djb2.hash("l49_52"); + assertEquals(-1153204821, hash); + } + +} diff --git a/cache/src/test/resources/cache.properties b/cache/src/test/resources/cache.properties index 03e58d141f..be42aebc22 100644 --- a/cache/src/test/resources/cache.properties +++ b/cache/src/test/resources/cache.properties @@ -1,3 +1,3 @@ -rs.version=${rs.version} -cache.version=${cache.version} +rs.version=@rs.version@ +cache.version=@cache.version@ diff --git a/checkstyle.xml b/checkstyle/checkstyle.xml similarity index 85% rename from checkstyle.xml rename to checkstyle/checkstyle.xml index e8669a742c..449e5ab7d9 100644 --- a/checkstyle.xml +++ b/checkstyle/checkstyle.xml @@ -1,6 +1,7 @@ - - 4.0.0 - - net.runelite - runelite-parent - 1.5.32-SNAPSHOT - - - Web API - http-api - - - nogit - false - - - - - com.squareup.okhttp3 - okhttp - 3.7.0 - - - com.google.code.gson - gson - - - org.slf4j - slf4j-api - - - org.projectlombok - lombok - provided - - - org.apache.commons - commons-csv - 1.4 - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - 1.7.12 - test - - - com.squareup.okhttp3 - mockwebserver - 3.7.0 - test - - - - - - - src/main/resources - true - - - - - pl.project13.maven - git-commit-id-plugin - 2.2.6 - - - query-git-info - - revision - - - false - false - - true - - - git.commit.id.abbrev - git.dirty - - - - - - - - diff --git a/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java b/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java index 472c241f72..b795453512 100644 --- a/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java +++ b/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java @@ -25,10 +25,7 @@ package net.runelite.http.api; import com.google.gson.Gson; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; -import java.util.concurrent.TimeUnit; +import com.google.gson.GsonBuilder; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.OkHttpClient; @@ -36,23 +33,44 @@ import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Properties; +import java.util.concurrent.TimeUnit; public class RuneLiteAPI { - private static final Logger logger = LoggerFactory.getLogger(RuneLiteAPI.class); + private static String version; + private static String upstreamVersion; + private static int rsVersion; public static final String RUNELITE_AUTH = "RUNELITE-AUTH"; - public static final OkHttpClient CLIENT; - public static final Gson GSON = new Gson(); - public static String userAgent; - + public static final OkHttpClient RLP_CLIENT; + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Logger logger = LoggerFactory.getLogger(RuneLiteAPI.class); private static final String BASE = "https://api.runelite.net"; + private static final String RLPLUS_BASE = "https://api.runelitepl.us"; + private static final String RLPLUS_SESSION = "https://session.runelitepl.us"; private static final String WSBASE = "https://api.runelite.net/ws"; private static final String STATICBASE = "https://static.runelite.net"; + private static final String MAVEN_METADATA = + "http://repo.runelite.net/net/runelite/runelite-parent/maven-metadata.xml"; private static final Properties properties = new Properties(); - private static String version; - private static int rsVersion; + private static String rlUserAgent; + private static String rlpUserAgent; static { @@ -62,15 +80,19 @@ public class RuneLiteAPI properties.load(in); version = properties.getProperty("runelite.version"); - rsVersion = Integer.parseInt(properties.getProperty("rs.version")); - String commit = properties.getProperty("runelite.commit"); + String rlpCommit = properties.getProperty("runelite.commit"); boolean dirty = Boolean.parseBoolean(properties.getProperty("runelite.dirty")); - userAgent = "RuneLite/" + version + "-" + commit + (dirty ? "+" : ""); + rlpUserAgent = "RuneLitePlus/" + version + "-" + rlpCommit + (dirty ? "+" : ""); + rlUserAgent = "RuneLitePlus/" + version; + rsVersion = Integer.parseInt(properties.getProperty("rs.version")); + + parseMavenVersion(); } catch (NumberFormatException e) { - throw new RuntimeException("Version string has not been substituted; Re-run maven"); + e.printStackTrace(); + throw new RuntimeException("Version string has not been substituted; Re-run Gradle"); } catch (IOException ex) { @@ -79,15 +101,34 @@ public class RuneLiteAPI CLIENT = new OkHttpClient.Builder() .pingInterval(30, TimeUnit.SECONDS) + .connectTimeout(8655, TimeUnit.MILLISECONDS) + .writeTimeout(8655, TimeUnit.MILLISECONDS) .addNetworkInterceptor(new Interceptor() { - @Override public Response intercept(Chain chain) throws IOException { Request userAgentRequest = chain.request() .newBuilder() - .header("User-Agent", userAgent) + .header("User-Agent", rlUserAgent) + .build(); + return chain.proceed(userAgentRequest); + } + }) + .build(); + + RLP_CLIENT = new OkHttpClient.Builder() + .pingInterval(30, TimeUnit.SECONDS) + .writeTimeout(5655, TimeUnit.MILLISECONDS) + .connectTimeout(2655, TimeUnit.MILLISECONDS) + .addNetworkInterceptor(new Interceptor() + { + @Override + public Response intercept(Chain chain) throws IOException + { + Request userAgentRequest = chain.request() + .newBuilder() + .header("User-Agent", rlpUserAgent) .build(); return chain.proceed(userAgentRequest); } @@ -95,16 +136,9 @@ public class RuneLiteAPI .build(); } - public static HttpUrl getSessionBase() + public static HttpUrl getRuneLitePlusSessionBase() { - final String prop = System.getProperty("runelite.session.url"); - - if (prop != null && !prop.isEmpty()) - { - return HttpUrl.parse(prop); - } - - return HttpUrl.parse(BASE + "/session"); + return HttpUrl.parse(RLPLUS_SESSION); } public static HttpUrl getApiBase() @@ -119,6 +153,11 @@ public class RuneLiteAPI return HttpUrl.parse(BASE + "/runelite-" + getVersion()); } + public static HttpUrl getPlusApiBase() + { + return HttpUrl.parse(RLPLUS_BASE + "/http-service-" + getRlpVersion()); + } + public static HttpUrl getStaticBase() { final String prop = System.getProperty("runelite.static.url"); @@ -145,12 +184,7 @@ public class RuneLiteAPI public static String getVersion() { - return version; - } - - public static void setVersion(String version) - { - RuneLiteAPI.version = version; + return upstreamVersion; } public static int getRsVersion() @@ -158,4 +192,60 @@ public class RuneLiteAPI return rsVersion; } + public static String getRlpVersion() + { + return version; + } + + private static byte[] downloadUrl(URL toDownload) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + InputStream stream; + try + { + byte[] chunk = new byte[4096]; + int bytesRead; + URLConnection conn = toDownload.openConnection(); + conn.setRequestProperty("User-Agent", rlpUserAgent); + stream = conn.getInputStream(); + + while ((bytesRead = stream.read(chunk)) > 0) + { + outputStream.write(chunk, 0, bytesRead); + } + stream.close(); + } + catch (IOException e) + { + e.printStackTrace(); + return null; + } + + return outputStream.toByteArray(); + } + + private static void parseMavenVersion() + { + try (ByteArrayInputStream fis = new ByteArrayInputStream(downloadUrl(new URL(MAVEN_METADATA)))) + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringElementContentWhitespace(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(fis); + NodeList versionList = doc.getElementsByTagName("version"); + for (int i = 0; i != versionList.getLength(); i++) + { + Node node = versionList.item(i); + if (node.getTextContent() != null) + { + upstreamVersion = node.getTextContent(); + } + } + } + catch (ParserConfigurationException | IOException | SAXException ex) + { + logger.error(null, ex); + } + } } diff --git a/http-api/src/main/java/net/runelite/http/api/animation/AnimationClient.java b/http-api/src/main/java/net/runelite/http/api/animation/AnimationClient.java new file mode 100644 index 0000000000..fd79680edd --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/animation/AnimationClient.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.animation; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AnimationClient +{ + private static final MediaType JSON = MediaType.parse("application/json"); + + private static final Logger logger = LoggerFactory.getLogger(AnimationClient.class); + + public void submit(AnimationRequest animationRequest) + { + String json = RuneLiteAPI.GSON.toJson(animationRequest); + + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("animation") + .build(); + + logger.debug("Built URI: {}", url); + + RequestBody body = RequestBody.Companion.create(json, JSON); + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + + try + { + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + logger.debug("animation response " + response.code()); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + RuneLiteAPI.RLP_CLIENT.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + logger.warn("unable to submit animations", e); + } + + @Override + public void onResponse(Call call, Response response) + { + try + { + if (!response.isSuccessful()) + { + logger.debug("unsuccessful animation response"); + } + } + finally + { + response.close(); + } + } + }); + } + + public List get() throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("animation") + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + InputStream in = response.body().byteStream(); + // CHECKSTYLE:OFF + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken>() + { + }.getType()); + // CHECKSTYLE:ON + } + catch (JsonParseException ex) + { + throw new IOException(ex); + } + } + + public AnimationKey get(int npcid) throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("animation") + .addPathSegment(Integer.toString(npcid)) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + InputStream in = response.body().byteStream(); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), AnimationKey.class); + } + catch (JsonParseException ex) + { + throw new IOException(ex); + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/animation/AnimationKey.java b/http-api/src/main/java/net/runelite/http/api/animation/AnimationKey.java new file mode 100644 index 0000000000..c4ce919326 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/animation/AnimationKey.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.animation; + +public class AnimationKey +{ + private int npcid; + private int[] animations; + + public int getNPCId() + { + return npcid; + } + + public void setNPCId(int npcid) + { + this.npcid = npcid; + } + + public int[] getAnimations() + { + return animations; + } + + public void setAnimations(int[] keys) + { + this.animations = keys; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/animation/AnimationRequest.java b/http-api/src/main/java/net/runelite/http/api/animation/AnimationRequest.java new file mode 100644 index 0000000000..9310c17ffc --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/animation/AnimationRequest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.animation; + +import java.util.ArrayList; +import java.util.List; + +public class AnimationRequest +{ + private int revision; + private List keys = new ArrayList<>(); + + public int getRevision() + { + return revision; + } + + public void setRevision(int revision) + { + this.revision = revision; + } + + public List getKeys() + { + return keys; + } + + public void addKey(AnimationKey key) + { + keys.add(key); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java index 79c4a889ed..19c4d1f81f 100644 --- a/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java +++ b/http-api/src/main/java/net/runelite/http/api/chat/ChatClient.java @@ -27,6 +27,8 @@ package net.runelite.http.api.chat; import com.google.gson.JsonParseException; import java.io.IOException; import java.io.InputStream; +import java.util.function.Predicate; +import java.util.regex.Pattern; import java.io.InputStreamReader; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; @@ -36,6 +38,12 @@ import okhttp3.Response; public class ChatClient { + + private static final RequestBody body = RequestBody.Companion.create(new byte[0], null); + private static final Predicate LAYOUT_VALIDATOR = Pattern + .compile("\\[[A-Z]+]:(\\s*\\w+\\s*(\\([A-Za-z]+\\))?,?)+") + .asPredicate(); + public boolean submitKc(String username, String boss, int kc) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -46,8 +54,9 @@ public class ChatClient .addQueryParameter("kc", Integer.toString(kc)) .build(); + Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -90,7 +99,7 @@ public class ChatClient .build(); Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -135,7 +144,7 @@ public class ChatClient .build(); Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -184,7 +193,7 @@ public class ChatClient .build(); Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -227,7 +236,7 @@ public class ChatClient .build(); Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -259,6 +268,31 @@ public class ChatClient } } + public boolean submitLayout(String username, String layout) throws IOException + { + if (!testLayout(layout)) + { + throw new IOException("Layout " + layout + " is not valid!"); + } + + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("layout") + .addQueryParameter("name", username) + .addQueryParameter("layout", layout) + .build(); + + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + return response.isSuccessful(); + } + } + public boolean submitDuels(String username, int wins, int losses, int winningStreak, int losingStreak) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -272,7 +306,7 @@ public class ChatClient .build(); Request request = new Request.Builder() - .post(RequestBody.create(null, new byte[0])) + .post(body) .url(url) .build(); @@ -282,6 +316,71 @@ public class ChatClient } } + public String getLayout(String username) throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("layout") + .addQueryParameter("name", username) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Unable to look up layout!"); + } + + final String layout = response.body().string(); + + if (!testLayout(layout)) + { + throw new IOException("Layout " + layout + " is not valid!"); + } + + return layout; + } + } + + public boolean testLayout(String layout) + { + return LAYOUT_VALIDATOR.test(layout); + } + + public House[] getHosts(int world, String location) throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("hosts") + .addQueryParameter("world", Integer.toString(world)) + .addQueryParameter("location", location) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Unable to look up hosts!"); + } + + InputStream in = response.body().byteStream(); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), House[].class); + + } + catch (JsonParseException ex) + { + throw new IOException(ex); + } + } + public Duels getDuels(String username) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -309,4 +408,61 @@ public class ChatClient throw new IOException(ex); } } -} + + public boolean submitHost(int world, String location, House house) throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("hosts") + .addQueryParameter("world", Integer.toString(world)) + .addQueryParameter("location", location) + .addQueryParameter("owner", house.getOwner()) + .addQueryParameter("guildedAltar", Boolean.toString(house.isGuildedAltarPresent())) + .addQueryParameter("occultAltar", Boolean.toString(house.isOccultAltarPresent())) + .addQueryParameter("spiritTree", Boolean.toString(house.isSpiritTreePresent())) + .addQueryParameter("fairyRing", Boolean.toString(house.isFairyRingPresent())) + .addQueryParameter("wildernessObelisk", Boolean.toString(house.isWildernessObeliskPresent())) + .addQueryParameter("repairStand", Boolean.toString(house.isRepairStandPresent())) + .addQueryParameter("combatDummy", Boolean.toString(house.isCombatDummyPresent())) + .build(); + + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + return response.isSuccessful(); + } + } + + public boolean removeHost(int world, String location, House house) throws IOException + { + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() + .addPathSegment("chat") + .addPathSegment("hosts") + .addQueryParameter("world", Integer.toString(world)) + .addQueryParameter("location", location) + .addQueryParameter("owner", house.getOwner()) + .addQueryParameter("guildedAltar", Boolean.toString(house.isGuildedAltarPresent())) + .addQueryParameter("occultAltar", Boolean.toString(house.isOccultAltarPresent())) + .addQueryParameter("spiritTree", Boolean.toString(house.isSpiritTreePresent())) + .addQueryParameter("fairyRing", Boolean.toString(house.isFairyRingPresent())) + .addQueryParameter("wildernessObelisk", Boolean.toString(house.isWildernessObeliskPresent())) + .addQueryParameter("repairStand", Boolean.toString(house.isRepairStandPresent())) + .addQueryParameter("combatDummy", Boolean.toString(house.isCombatDummyPresent())) + .addQueryParameter("remove", Boolean.toString(true)) + .build(); + + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + + try (Response response = RuneLiteAPI.RLP_CLIENT.newCall(request).execute()) + { + return response.isSuccessful(); + } + } +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/chat/House.java b/http-api/src/main/java/net/runelite/http/api/chat/House.java new file mode 100644 index 0000000000..5ae614e129 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/chat/House.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, Spedwards + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.chat; + +import lombok.Data; +import net.runelite.http.api.RuneLiteAPI; + +@Data +public class House +{ + private String owner; + private boolean guildedAltarPresent; + private boolean occultAltarPresent; + private boolean spiritTreePresent; + private boolean fairyRingPresent; + private boolean wildernessObeliskPresent; + private boolean repairStandPresent; + private boolean combatDummyPresent; + + @Override + public String toString() + { + return RuneLiteAPI.GSON.toJson(this); + } +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java index 4d82976454..fbdb2610e6 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigClient.java @@ -140,4 +140,4 @@ public class ConfigClient } }); } -} +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java index 783d68361e..33db4064e9 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java +++ b/http-api/src/main/java/net/runelite/http/api/config/ConfigEntry.java @@ -48,4 +48,4 @@ public class ConfigEntry { this.value = value; } -} +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java index 7b256e0c7a..7d5b86d9b2 100644 --- a/http-api/src/main/java/net/runelite/http/api/config/Configuration.java +++ b/http-api/src/main/java/net/runelite/http/api/config/Configuration.java @@ -40,4 +40,4 @@ public class Configuration { return config; } -} +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/discord/DiscordClient.java b/http-api/src/main/java/net/runelite/http/api/discord/DiscordClient.java new file mode 100644 index 0000000000..0640494a62 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/DiscordClient.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord; + +import com.google.gson.Gson; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Slf4j +public class DiscordClient +{ + public static final Gson gson = new Gson(); + private static final MediaType JSON = MediaType.parse("application/json"); + + public void message(HttpUrl url, DiscordMessage discordMessage) + { + log.debug("Message being sent"); + message(url, discordMessage, 0, 5); + } + + private void message(HttpUrl url, DiscordMessage discordMessage, int retryAttempt, int maxAttempts) + { + RequestBody body = RequestBody.Companion.create(gson.toJson(discordMessage), JSON); + Request request = new Request.Builder() + .post(body) + .url(url) + .build(); + + log.debug("Attempting to message with {}", discordMessage); + + RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() + { + + @Override + public void onFailure(Call call, IOException e) + { + log.warn("Unable to submit discord post.", e); + if (retryAttempt < maxAttempts) + { + message(url, discordMessage, retryAttempt + 1, maxAttempts); + } + } + + @Override + public void onResponse(Call call, Response response) throws IOException + { + try + { + if (response.body() == null) + { + log.debug("API Call - Reponse was null."); + return; + } + if (response.body().string().contains("You are being rate limited") && retryAttempt < maxAttempts) + { + log.debug("You are being rate limited, retrying..."); + message(url, discordMessage, retryAttempt + 1, maxAttempts); + } + } + finally + { + response.close(); + log.debug("Submitted discord log record"); + } + } + }); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/DiscordEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/DiscordEmbed.java new file mode 100644 index 0000000000..9de5f42331 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/DiscordEmbed.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import net.runelite.http.api.discord.embed.AuthorEmbed; +import net.runelite.http.api.discord.embed.FieldEmbed; +import net.runelite.http.api.discord.embed.FooterEmbed; +import net.runelite.http.api.discord.embed.ImageEmbed; +import net.runelite.http.api.discord.embed.ProviderEmbed; +import net.runelite.http.api.discord.embed.ThumbnailEmbed; +import net.runelite.http.api.discord.embed.VideoEmbed; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@ToString +public class DiscordEmbed +{ + String title; + String type; + String description; + String url; + String timestamp; + String iconurl; + String color; + FooterEmbed footer; + ImageEmbed image; + ThumbnailEmbed thumbnail; + VideoEmbed video; + ProviderEmbed provider; + AuthorEmbed author; + @Builder.Default + List fields = new ArrayList<>(); + + public DiscordEmbed() + { + + } + + public DiscordEmbed(String title, String description) + { + this(title, description, null); + } + + public DiscordEmbed(String title, String description, String url) + { + setTitle(title); + setDescription(description); + setUrl(url); + } + + public static DiscordMessage toDiscordMessage(DiscordEmbed embed, String username, String avatarURL) + { + return DiscordMessage.builder() + .username(username) + .avatarUrl(avatarURL) + .content("") + .embed(embed) + .build(); + } + + public DiscordMessage toDiscordMessage(String username, String avatarUrl) + { + return DiscordEmbed.toDiscordMessage(this, username, avatarUrl); + } + + public static class DiscordEmbedBuilder + { + List fields = new ArrayList<>(); + + public DiscordEmbedBuilder field(FieldEmbed field) + { + fields.add(field); + return this; + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/DiscordMessage.java b/http-api/src/main/java/net/runelite/http/api/discord/DiscordMessage.java new file mode 100644 index 0000000000..061568da9d --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/DiscordMessage.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord; + +import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@ToString +public class DiscordMessage +{ + String username; + String content; + @SerializedName("avatar_url") + String avatarUrl; + @SerializedName("tts") + boolean textToSpeech; + List embeds = new ArrayList<>(); + + public DiscordMessage() + { + + } + + public DiscordMessage(String username, String content, String avatar_url) + { + this(username, content, avatar_url, false); + } + + public DiscordMessage(String username, String content, String avatar_url, boolean tts) + { + setUsername(username); + setContent(content); + setAvatarUrl(avatar_url); + setTextToSpeech(tts); + } + + public void setUsername(String username) + { + if (username != null) + { + this.username = username.substring(0, Math.min(31, username.length())); + } + else + { + this.username = null; + } + } + + public static class DiscordMessageBuilder + { + List embeds = new ArrayList<>(); + + public DiscordMessageBuilder embed(DiscordEmbed embed) + { + embeds.add(embed); + return this; + } + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/AuthorEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/AuthorEmbed.java new file mode 100644 index 0000000000..46d1f34213 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/AuthorEmbed.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class AuthorEmbed +{ + String name; + String url; + String icon_url; + String proxy_icon_url; +} \ No newline at end of file diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/FieldEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/FieldEmbed.java new file mode 100644 index 0000000000..7abf9c3832 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/FieldEmbed.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class FieldEmbed +{ + @NonNull + String name; + @NonNull + String value; + boolean inline; +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/FooterEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/FooterEmbed.java new file mode 100644 index 0000000000..73185c0d7f --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/FooterEmbed.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class FooterEmbed +{ + String text; + String icon_url; + String proxy_icon_url; +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/ImageEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/ImageEmbed.java new file mode 100644 index 0000000000..df8c511384 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/ImageEmbed.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class ImageEmbed +{ + String url; + String proxy_url; + int height; + int width; +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/ProviderEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/ProviderEmbed.java new file mode 100644 index 0000000000..0ac555189e --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/ProviderEmbed.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class ProviderEmbed +{ + String name; + String url; +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/ThumbnailEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/ThumbnailEmbed.java new file mode 100644 index 0000000000..8c17e4dfc8 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/ThumbnailEmbed.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class ThumbnailEmbed +{ + String url; + String proxy_url; + int height; + int width; +} diff --git a/http-api/src/main/java/net/runelite/http/api/discord/embed/VideoEmbed.java b/http-api/src/main/java/net/runelite/http/api/discord/embed/VideoEmbed.java new file mode 100644 index 0000000000..af396f0ebf --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/discord/embed/VideoEmbed.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Forsco + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.http.api.discord.embed; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class VideoEmbed +{ + String url; + int height; + int width; +} diff --git a/http-api/src/main/java/net/runelite/http/api/examine/ExamineClient.java b/http-api/src/main/java/net/runelite/http/api/examine/ExamineClient.java index 5997799c2e..43096a61e1 100644 --- a/http-api/src/main/java/net/runelite/http/api/examine/ExamineClient.java +++ b/http-api/src/main/java/net/runelite/http/api/examine/ExamineClient.java @@ -67,9 +67,10 @@ public class ExamineClient logger.debug("Built URI: {}", url); + RequestBody body = RequestBody.Companion.create(text, TEXT); Request request = new Request.Builder() .url(url) - .post(RequestBody.create(TEXT, text)) + .post(body) .build(); RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java index a1b97bc390..f016468e92 100644 --- a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java @@ -53,9 +53,10 @@ public class GrandExchangeClient .addPathSegment("ge") .build(); + RequestBody body = RequestBody.Companion.create(GSON.toJson(grandExchangeTrade), JSON); Request request = new Request.Builder() .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) - .post(RequestBody.create(JSON, GSON.toJson(grandExchangeTrade))) + .post(body) .url(url) .build(); diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java index 17784dd67b..0e34a05742 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java @@ -26,6 +26,7 @@ package net.runelite.http.api.item; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; +import io.reactivex.Observable; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; @@ -112,7 +113,7 @@ public class ItemClient } } - public BufferedImage getIcon(int itemId) throws IOException + public Observable getIcon(int itemId) { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("item") @@ -126,23 +127,26 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - logger.debug("Error grabbing icon {}: {}", itemId, response); - return null; - } + if (!response.isSuccessful()) + { + logger.debug("Error grabbing icon {}: {}", itemId, response); + return Observable.just(null); + } - InputStream in = response.body().byteStream(); - synchronized (ImageIO.class) - { - return ImageIO.read(in); + InputStream in = response.body().byteStream(); + synchronized (ImageIO.class) + { + return Observable.just(ImageIO.read(in)); + } } - } + }); } - public SearchResult search(String itemName) throws IOException + public Observable search(String itemName) { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("item") @@ -152,28 +156,31 @@ public class ItemClient logger.debug("Built URI: {}", url); - Request request = new Request.Builder() - .url(url) - .build(); - - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - logger.debug("Error looking up item {}: {}", itemName, response); - return null; - } + if (!response.isSuccessful()) + { + logger.debug("Error looking up item {}: {}", itemName, response); + return Observable.just(null); + } - InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), SearchResult.class); - } - catch (JsonParseException ex) - { - throw new IOException(ex); - } + InputStream in = response.body().byteStream(); + return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), SearchResult.class)); + } + catch (JsonParseException ex) + { + return Observable.error(ex); + } + }); } - public ItemPrice[] getPrices() throws IOException + public Observable getPrices() { HttpUrl.Builder urlBuilder = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("item") @@ -183,28 +190,32 @@ public class ItemClient logger.debug("Built URI: {}", url); - Request request = new Request.Builder() - .url(url) - .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - logger.warn("Error looking up prices: {}", response); - return null; - } + if (!response.isSuccessful()) + { + logger.warn("Error looking up prices: {}", response); + return Observable.just(null); + } - InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), ItemPrice[].class); - } - catch (JsonParseException ex) - { - throw new IOException(ex); - } + InputStream in = response.body().byteStream(); + return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), ItemPrice[].class)); + } + catch (JsonParseException ex) + { + return Observable.error(ex); + } + }); } - public Map getStats() throws IOException + public Observable> getStats() { HttpUrl.Builder urlBuilder = RuneLiteAPI.getStaticBase().newBuilder() .addPathSegment("item") @@ -215,27 +226,31 @@ public class ItemClient logger.debug("Built URI: {}", url); - Request request = new Request.Builder() - .url(url) - .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - logger.warn("Error looking up item stats: {}", response); - return null; + if (!response.isSuccessful()) + { + logger.warn("Error looking up item stats: {}", response); + return Observable.just(null); + } + + InputStream in = response.body().byteStream(); + final Type typeToken = new TypeToken>() + { + }.getType(); + return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), typeToken)); } - - InputStream in = response.body().byteStream(); - final Type typeToken = new TypeToken>() + catch (JsonParseException ex) { - }.getType(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), typeToken); - } - catch (JsonParseException ex) - { - throw new IOException(ex); - } + return Observable.error(ex); + } + }); } } diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java b/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java index 3ebc17970f..088d0536ca 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java @@ -29,6 +29,7 @@ import lombok.Value; @Value public class ItemStats { + private String name; private boolean quest; private boolean equipable; private double weight; @@ -76,7 +77,7 @@ public class ItemStats newEquipment = equipment; } - return new ItemStats(quest, equipable, newWeight, newEquipment); + return new ItemStats(name, quest, equipable, newWeight, newEquipment); } } diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java index fc945220f1..993959ee88 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java @@ -26,8 +26,10 @@ package net.runelite.http.api.loottracker; import java.time.Instant; import java.util.Collection; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; @Data @@ -36,7 +38,26 @@ import lombok.NoArgsConstructor; public class LootRecord { private String eventId; + @Getter + private String username; private LootRecordType type; private Collection drops; private Instant time; + + /** + * constructor for lootRecords retrieved by http api (doesn't store/retrieve username) + * @param eventId - the eventID or the name/title of the LootRecord + * @param type - The LootRecordType + * @param gameItems - the list of items/quantities + * @param time - the Instant that the Loot Record was received + */ + public LootRecord(String eventId, LootRecordType type, List gameItems, Instant time) + { + // Insert blank username + this.username = ""; + this.eventId = eventId; + this.type = type; + this.drops = gameItems; + this.time = time; + } } diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java index c91817b410..daf515b896 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java @@ -29,5 +29,6 @@ public enum LootRecordType NPC, PLAYER, EVENT, + DEATH, UNKNOWN } diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java index 59d1a567ff..c6a52ea419 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java @@ -59,9 +59,10 @@ public class LootTrackerClient .addPathSegment("loottracker") .build(); + RequestBody body = RequestBody.Companion.create(GSON.toJson(lootRecords), JSON); Request request = new Request.Builder() .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) - .post(RequestBody.create(JSON, GSON.toJson(lootRecords))) + .post(body) .url(url) .build(); diff --git a/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java index c01337fdc0..1739125f8c 100644 --- a/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java +++ b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java @@ -25,6 +25,7 @@ package net.runelite.http.api.osbuddy; import com.google.gson.JsonParseException; +import io.reactivex.Observable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,7 +38,7 @@ import okhttp3.Response; @Slf4j public class OSBGrandExchangeClient { - public OSBGrandExchangeResult lookupItem(int itemId) throws IOException + public Observable lookupItem(int itemId) { final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("osb") @@ -47,23 +48,26 @@ public class OSBGrandExchangeClient log.debug("Built URI: {}", url); - final Request request = new Request.Builder() - .url(url) - .build(); - - try (final Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + Request request = new Request.Builder() + .url(url) + .build(); + + try (final Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - throw new IOException("Error looking up item id: " + response); - } + if (!response.isSuccessful()) + { + return Observable.error(new IOException("Error looking up item id: " + response)); + } - final InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), OSBGrandExchangeResult.class); - } - catch (JsonParseException ex) - { - throw new IOException(ex); - } + final InputStream in = response.body().byteStream(); + return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), OSBGrandExchangeResult.class)); + } + catch (JsonParseException e) + { + return Observable.error(e); + } + }); } } diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java index 668e088404..13559bdd3b 100644 --- a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java @@ -26,6 +26,9 @@ package net.runelite.http.api.worlds; import com.google.gson.JsonParseException; +import io.reactivex.Observable; +import java.io.InputStream; +import java.io.InputStreamReader; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; import okhttp3.Request; @@ -33,15 +36,11 @@ import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - public class WorldClient { private static final Logger logger = LoggerFactory.getLogger(WorldClient.class); - public WorldResult lookupWorlds() throws IOException + public Observable lookupWorlds() { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("worlds.js") @@ -49,24 +48,27 @@ public class WorldClient logger.debug("Built URI: {}", url); - Request request = new Request.Builder() - .url(url) - .build(); - - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + return Observable.defer(() -> { - if (!response.isSuccessful()) + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - logger.debug("Error looking up worlds: {}", response); - return null; - } + if (!response.isSuccessful()) + { + logger.debug("Error looking up worlds: {}", response); + return Observable.just(null); + } - InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), WorldResult.class); - } - catch (JsonParseException ex) - { - throw new IOException(ex); - } + InputStream in = response.body().byteStream(); + return Observable.just(RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), WorldResult.class)); + } + catch (JsonParseException ex) + { + return Observable.error(ex); + } + }); } } diff --git a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java index d732c110cd..227e61eb56 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketMessage.java @@ -24,7 +24,9 @@ */ package net.runelite.http.api.ws; -public class WebsocketMessage +import net.runelite.api.events.Event; + +public class WebsocketMessage implements Event { protected boolean _party; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java index b517e773a2..588736887d 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/LoginResponse.java @@ -24,13 +24,14 @@ */ package net.runelite.http.api.ws.messages; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; /** * Called after a successful login to the server * @author Adam */ -public class LoginResponse extends WebsocketMessage +public class LoginResponse extends WebsocketMessage implements Event { private String username; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java index 21aed0f653..e68910567b 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java @@ -27,11 +27,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Value; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; @Value @EqualsAndHashCode(callSuper = true) -public class Join extends WebsocketMessage +public class Join extends WebsocketMessage implements Event { private final UUID partyId; private final String name; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java index e284ff0cf8..0d487a3be6 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Part.java @@ -24,8 +24,9 @@ */ package net.runelite.http.api.ws.messages.party; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; -public class Part extends WebsocketMessage +public class Part extends WebsocketMessage implements Event { } diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java index 480e2660c1..c61ea86187 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyChatMessage.java @@ -24,10 +24,12 @@ */ package net.runelite.http.api.ws.messages.party; +import lombok.EqualsAndHashCode; import lombok.Value; - +import net.runelite.api.events.Event; @Value -public class PartyChatMessage extends PartyMemberMessage +@EqualsAndHashCode(callSuper = true) +public class PartyChatMessage extends PartyMemberMessage implements Event { private final String value; } diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java index 9d5cab8545..3a4f330343 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMemberMessage.java @@ -3,10 +3,11 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; import lombok.Getter; import lombok.Setter; +import net.runelite.api.events.Event; @Getter @Setter -public abstract class PartyMemberMessage extends PartyMessage +public abstract class PartyMemberMessage extends PartyMessage implements Event { private UUID memberId; } diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java index 709457ed8c..2f9ef90d92 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/PartyMessage.java @@ -24,9 +24,10 @@ */ package net.runelite.http.api.ws.messages.party; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; -public abstract class PartyMessage extends WebsocketMessage +public abstract class PartyMessage extends WebsocketMessage implements Event { public PartyMessage() { diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java index 7f940e8060..fd024c6cf3 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java @@ -27,11 +27,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Value; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; @Value @EqualsAndHashCode(callSuper = true) -public class UserJoin extends WebsocketMessage +public class UserJoin extends WebsocketMessage implements Event { private final UUID memberId; private final UUID partyId; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java index e80c6002bd..1dcd277c02 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java @@ -27,11 +27,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; import lombok.EqualsAndHashCode; import lombok.Value; +import net.runelite.api.events.Event; import net.runelite.http.api.ws.WebsocketMessage; @Value @EqualsAndHashCode(callSuper = true) -public class UserPart extends WebsocketMessage +public class UserPart extends WebsocketMessage implements Event { private final UUID memberId; } diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java index c95038c9fa..bcb6ca0ee7 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java @@ -26,9 +26,10 @@ package net.runelite.http.api.ws.messages.party; import lombok.EqualsAndHashCode; import lombok.Value; +import net.runelite.api.events.Event; @Value @EqualsAndHashCode(callSuper = true) -public class UserSync extends PartyMemberMessage +public class UserSync extends PartyMemberMessage implements Event { } diff --git a/http-api/src/main/java/net/runelite/http/api/xtea/XteaClient.java b/http-api/src/main/java/net/runelite/http/api/xtea/XteaClient.java index e4ee2c22b9..c5d5b4c89a 100644 --- a/http-api/src/main/java/net/runelite/http/api/xtea/XteaClient.java +++ b/http-api/src/main/java/net/runelite/http/api/xtea/XteaClient.java @@ -51,18 +51,19 @@ public class XteaClient { String json = RuneLiteAPI.GSON.toJson(xteaRequest); - HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() .addPathSegment("xtea") .build(); logger.debug("Built URI: {}", url); + RequestBody body = RequestBody.Companion.create(json, JSON); Request request = new Request.Builder() - .post(RequestBody.create(JSON, json)) + .post(body) .url(url) .build(); - RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() + RuneLiteAPI.RLP_CLIENT.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) @@ -90,7 +91,7 @@ public class XteaClient public List get() throws IOException { - HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() .addPathSegment("xtea") .build(); @@ -102,7 +103,9 @@ public class XteaClient { InputStream in = response.body().byteStream(); // CHECKSTYLE:OFF - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken>() { }.getType()); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken>() + { + }.getType()); // CHECKSTYLE:ON } catch (JsonParseException ex) @@ -113,7 +116,7 @@ public class XteaClient public XteaKey get(int region) throws IOException { - HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + HttpUrl url = RuneLiteAPI.getPlusApiBase().newBuilder() .addPathSegment("xtea") .addPathSegment(Integer.toString(region)) .build(); diff --git a/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java b/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java index c108f223cb..668a93038d 100644 --- a/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java +++ b/http-api/src/main/java/net/runelite/http/api/xtea/XteaKey.java @@ -27,7 +27,7 @@ package net.runelite.http.api.xtea; public class XteaKey { private int region; - private int keys[]; + private int[] keys; public int getRegion() { diff --git a/http-api/src/main/resources/runelite.properties b/http-api/src/main/resources/runelite.properties index 3abfd6390e..7eb618db7a 100644 --- a/http-api/src/main/resources/runelite.properties +++ b/http-api/src/main/resources/runelite.properties @@ -1,4 +1,4 @@ -runelite.version=${project.version} -rs.version=${rs.version} -runelite.commit=${git.commit.id.abbrev} -runelite.dirty=${git.dirty} \ No newline at end of file +runelite.version=@projectver@ +rs.version=@rsver@ +runelite.commit=@gitcommit@ +runelite.dirty=@gitdirty@ \ No newline at end of file diff --git a/http-service-plus/build.gradle b/http-service-plus/build.gradle new file mode 100644 index 0000000000..3b3d4778d2 --- /dev/null +++ b/http-service-plus/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'war' + +description = 'Web Service Plus' + +dependencies { + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok + + api project(':cache') + api project(':http-api') + api project(':http-service') + + implementation group: 'com.google.code.gson', name: 'gson', version: gson + implementation group: 'com.google.guava', name: 'guava', version: guava + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: okhttp3 + implementation group: 'org.springframework', name: 'spring-jdbc', version: springJdbc + implementation group: 'org.springframework.boot', name: 'spring-boot-devtools', version: springboot + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springboot + implementation group: 'org.sql2o', name: 'sql2o', version: sql2o + implementation(group: 'redis.clients', name: 'jedis', version: jedis) { + exclude(module: 'commons-pool2') + } + + providedCompile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: mariadbJdbc + providedCompile group: 'org.projectlombok', name: 'lombok', version: lombok + providedCompile group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat', version: springboot +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service-plus/src/main/java/net/runelite/http/service/SpringBootWebApplication.java new file mode 100644 index 0000000000..50d94e97a9 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service; + +import ch.qos.logback.classic.LoggerContext; +import com.google.common.base.Strings; +import java.io.IOException; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.service.util.InstantConverter; +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import org.slf4j.ILoggerFactory; +import org.slf4j.impl.StaticLoggerBinder; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.sql2o.Sql2o; +import org.sql2o.converters.Converter; +import org.sql2o.quirks.NoQuirks; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@EnableScheduling +@Slf4j +public class SpringBootWebApplication extends SpringBootServletInitializer +{ + @Bean + protected ServletContextListener listener() + { + return new ServletContextListener() + { + @Override + public void contextInitialized(ServletContextEvent sce) + { + log.info("RuneLitePlus API started"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + // Destroy okhttp client + OkHttpClient client = RuneLiteAPI.CLIENT; + client.dispatcher().executorService().shutdown(); + client.connectionPool().evictAll(); + try + { + Cache cache = client.cache(); + if (cache != null) + { + cache.close(); + } + } + catch (IOException ex) + { + log.warn(null, ex); + } + + log.info("RuneLitePlus API stopped"); + } + + }; + } + + @ConfigurationProperties(prefix = "datasource.runeliteplus") + @Bean("dataSourceRuneLite") + public DataSourceProperties dataSourceProperties() + { + return new DataSourceProperties(); + } + + @ConfigurationProperties(prefix = "datasource.runeliteplus-cache") + @Bean("dataSourceRuneLiteCache") + public DataSourceProperties dataSourcePropertiesCache() + { + return new DataSourceProperties(); + } + + @ConfigurationProperties(prefix = "datasource.runeliteplus-tracker") + @Bean("dataSourceRuneLiteTracker") + public DataSourceProperties dataSourcePropertiesTracker() + { + return new DataSourceProperties(); + } + + @Bean(value = "runelite", destroyMethod = "") + public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean(value = "runelite-cache2", destroyMethod = "") + public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean(value = "runelite-tracker", destroyMethod = "") + public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean("Runelite SQL2O") + public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource) + { + return createSql2oFromDataSource(dataSource); + } + + @Bean("Runelite Cache SQL2O") + public Sql2o cacheSql2o(@Qualifier("runelite-cache2") DataSource dataSource) + { + return createSql2oFromDataSource(dataSource); + } + + @Bean("Runelite XP Tracker SQL2O") + public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource) + { + return createSql2oFromDataSource(dataSource); + } + + private static DataSource getDataSource(DataSourceProperties dataSourceProperties) + { + if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName())) + { + // Use JNDI provided datasource, which is already configured with pooling + JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); + return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName()); + } + else + { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + } + + private static Sql2o createSql2oFromDataSource(final DataSource dataSource) + { + final Map converters = new HashMap<>(); + converters.put(Instant.class, new InstantConverter()); + return new Sql2o(dataSource, new NoQuirks(converters)); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(SpringBootWebApplication.class); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException + { + super.onStartup(servletContext); + ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + if (loggerFactory instanceof LoggerContext) + { + LoggerContext loggerContext = (LoggerContext) loggerFactory; + loggerContext.setPackagingDataEnabled(false); + log.debug("Disabling logback packaging data"); + } + } + + public static void main(String[] args) + { + SpringApplication.run(SpringBootWebApplication.class, args); + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java b/http-service-plus/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java new file mode 100644 index 0000000000..841eb28eeb --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service; + +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@EnableWebMvc +public class SpringWebMvcConfigurer implements WebMvcConfigurer +{ + /** + * Configure .js as application/json to trick Cloudflare into caching json responses + */ + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) + { + configurer.mediaType("js", MediaType.APPLICATION_JSON); + } + + /** + * Use GSON instead of Jackson for JSON serialization + * @param converters + */ + @Override + public void extendMessageConverters(List> converters) + { + // Could not figure out a better way to force GSON + converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance); + + GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter(); + gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON); + converters.add(gsonHttpMessageConverter); + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationCache.java b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationCache.java new file mode 100644 index 0000000000..05f8e5ae11 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationCache.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.animation; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +class AnimationCache +{ + private int npcid; + private int anim1; + private int anim2; + private int anim3; + private int anim4; + private int anim5; + private int anim6; + private int anim7; + private int anim8; + private int anim9; + private int anim10; +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationController.java b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationController.java new file mode 100644 index 0000000000..c588b8193d --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationController.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.animation; + +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.http.api.animation.AnimationKey; +import net.runelite.http.api.animation.AnimationRequest; +import net.runelite.http.service.util.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/animation") +public class AnimationController +{ + @Autowired + private AnimationEndpoint animationService; + + @RequestMapping(method = POST) + public void submit(@RequestBody AnimationRequest animationRequest) + { + animationService.submit(animationRequest); + } + + @GetMapping + public List get() + { + return animationService.get().stream() + .map(AnimationController::entryToKey) + .collect(Collectors.toList()); + } + + @GetMapping("/{npcid}") + public AnimationKey getRegion(@PathVariable int npcid) + { + AnimationEntry animationEntry = animationService.getNPC(npcid); + if (animationEntry == null) + { + throw new NotFoundException(); + } + + return entryToKey(animationEntry); + } + + private static AnimationKey entryToKey(AnimationEntry xe) + { + AnimationKey animationKey = new AnimationKey(); + animationKey.setNPCId(xe.getNPCId()); + animationKey.setAnimations(new int[] + { + xe.getAnimations()[0], + xe.getAnimations()[1], + xe.getAnimations()[2], + xe.getAnimations()[3], + xe.getAnimations()[4], + xe.getAnimations()[5], + xe.getAnimations()[6], + xe.getAnimations()[7], + xe.getAnimations()[8], + xe.getAnimations()[9], + }); + return animationKey; + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEndpoint.java b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEndpoint.java new file mode 100644 index 0000000000..d33ca43051 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEndpoint.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.animation; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.List; +import net.runelite.http.api.animation.AnimationKey; +import net.runelite.http.api.animation.AnimationRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +public class AnimationEndpoint +{ + private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `animation` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `npcid` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " `rev` int(11) NOT NULL,\n" + + " `anim1` int(11) NOT NULL,\n" + + " `anim2` int(11),\n" + + " `anim3` int(11),\n" + + " `anim4` int(11),\n" + + " `anim5` int(11),\n" + + " `anim6` int(11),\n" + + " `anim7` int(11),\n" + + " `anim8` int(11),\n" + + " `anim9` int(11),\n" + + " `anim10` int(11),\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `npcid` (`npcid`,`time`)\n" + + ") ENGINE=InnoDB"; + + private final Sql2o sql2o; + + private final Cache keyCache = CacheBuilder.newBuilder() + .maximumSize(1024) + .build(); + + @Autowired + public AnimationEndpoint( + @Qualifier("Runelite SQL2O") Sql2o sql2o + ) + { + this.sql2o = sql2o; + + try (Connection con = sql2o.beginTransaction()) + { + con.createQuery(CREATE_SQL) + .executeUpdate(); + } + } + + private AnimationEntry findLatestAnimations(Connection con, int npcid) + { + return con.createQuery("select npcid, time, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation " + + "where npcid = :npcid " + + "order by time desc " + + "limit 1") + .addParameter("npcid", npcid) + .executeAndFetchFirst(AnimationEntry.class); + } + + public void submit(AnimationRequest animationRequest) + { + boolean cached = true; + for (AnimationKey key : animationRequest.getKeys()) + { + int npcid = key.getNPCId(); + int[] animations = key.getAnimations(); + + AnimationCache animationCache = keyCache.getIfPresent(npcid); + if (animationCache == null + || animationCache.getAnim1() != animations[0] + || animationCache.getAnim2() != animations[1] + || animationCache.getAnim3() != animations[2] + || animationCache.getAnim4() != animations[3] + || animationCache.getAnim5() != animations[4] + || animationCache.getAnim6() != animations[5] + || animationCache.getAnim7() != animations[6] + || animationCache.getAnim8() != animations[7] + || animationCache.getAnim9() != animations[8] + || animationCache.getAnim10() != animations[9]) + { + cached = false; + keyCache.put(npcid, new AnimationCache(npcid, animations[0], animations[1], animations[2], animations[3], animations[4], animations[5], animations[6], animations[7], animations[8], animations[9])); + } + } + + if (cached) + { + return; + } + + try (Connection con = sql2o.beginTransaction()) + { + Query query = null; + + for (AnimationKey key : animationRequest.getKeys()) + { + int npcid = key.getNPCId(); + int[] animations = key.getAnimations(); + + AnimationEntry animationEntry = findLatestAnimations(con, npcid); + + if (animations.length != 10) + { + throw new IllegalArgumentException("Key length must be 10"); + } + + // already have these? + if (animationEntry != null + && animationEntry.getAnimations()[0] == animations[0] + && animationEntry.getAnimations()[1] == animations[1] + && animationEntry.getAnimations()[2] == animations[2] + && animationEntry.getAnimations()[3] == animations[3] + && animationEntry.getAnimations()[4] == animations[4] + && animationEntry.getAnimations()[5] == animations[5] + && animationEntry.getAnimations()[6] == animations[6] + && animationEntry.getAnimations()[7] == animations[7] + && animationEntry.getAnimations()[8] == animations[8] + && animationEntry.getAnimations()[9] == animations[9]) + { + continue; + } + + + if (query == null) + { + query = con.createQuery("insert into animation (npcid, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10) " + + "values (:npcid, :rev, :anim1, :anim2, :anim3, :anim4, anim5, anim6, anim7, anim8, anim9, anim10)"); + } + + query.addParameter("npcid", npcid) + .addParameter("rev", animationRequest.getRevision()) + .addParameter("anim1", animations[0]) + .addParameter("anim2", animations[1]) + .addParameter("anim3", animations[2]) + .addParameter("anim4", animations[3]) + .addParameter("anim5", animations[4]) + .addParameter("anim6", animations[5]) + .addParameter("anim7", animations[6]) + .addParameter("anim8", animations[7]) + .addParameter("anim9", animations[8]) + .addParameter("anim10", animations[9]) + .addToBatch(); + } + + if (query != null) + { + query.executeBatch(); + con.commit(false); + } + } + } + + public List get() + { + try (Connection con = sql2o.open()) + { + return con.createQuery( + "select t1.npcid, t2.time, t2.rev, t2.anim1, t2.anim2, t2.anim3, t2.anim4, t2.anim5, t2.anim6, t2.anim7, t2.anim8, t2.anim9, t2.anim10 from " + + "(select npcid,max(id) as id from animation group by npcid) t1 " + + "join animation t2 on t1.id = t2.id") + .executeAndFetch(AnimationEntry.class); + } + } + + public AnimationEntry getNPC(int npcid) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select npcid, time, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation " + + "where npcid = :npcid order by time desc limit 1") + .addParameter("npcid", npcid) + .executeAndFetchFirst(AnimationEntry.class); + } + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEntry.java b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEntry.java new file mode 100644 index 0000000000..af8ed67257 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/animation/AnimationEntry.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.animation; + +import java.time.Instant; + +public class AnimationEntry +{ + private int npcid; + private Instant time; + private int rev; + private int[] animations; + + public int getNPCId() + { + return npcid; + } + + public void setNPCId(int npcid) + { + this.npcid = npcid; + } + + public Instant getTime() + { + return time; + } + + public void setTime(Instant time) + { + this.time = time; + } + + public int getRev() + { + return rev; + } + + public void setRev(int rev) + { + this.rev = rev; + } + + public int[] getAnimations() + { + return animations; + } + + public void setAnimations(int[] animations) + { + this.animations = animations; + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatController.java b/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatController.java new file mode 100644 index 0000000000..df287e6512 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatController.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.chat; + +import java.util.regex.Pattern; +import com.google.common.base.Strings; +import net.runelite.http.api.chat.House; +import net.runelite.http.service.util.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/chat") +public class ChatController +{ + private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]"); + private static final int STRING_MAX_LENGTH = 50; + + @Autowired + private ChatService chatService; + + @PostMapping("/layout") + public void submitLayout(@RequestParam String name, @RequestParam String layout) + { + if (Strings.isNullOrEmpty(layout)) + { + return; + } + + chatService.setLayout(name, layout); + } + + @GetMapping("/layout") + public String getLayout(@RequestParam String name) + { + String layout = chatService.getLayout(name); + if (layout == null) + { + throw new NotFoundException(); + } + return layout; + } + + @PostMapping("/hosts") + public void submitHost(@RequestParam int world, @RequestParam String location, @RequestParam String owner, @RequestParam boolean guildedAltar, @RequestParam boolean occultAltar, @RequestParam boolean spiritTree, @RequestParam boolean fairyRing, @RequestParam boolean wildernessObelisk, @RequestParam boolean repairStand, @RequestParam boolean combatDummy, @RequestParam(required = false, defaultValue = "false") boolean remove) + { + if (!location.equals("Rimmington") && !location.equals("Yanille")) + { + return; + } + + House house = new House(); + house.setOwner(owner); + house.setGuildedAltarPresent(guildedAltar); + house.setOccultAltarPresent(occultAltar); + house.setSpiritTreePresent(spiritTree); + house.setFairyRingPresent(fairyRing); + house.setWildernessObeliskPresent(wildernessObelisk); + house.setRepairStandPresent(repairStand); + house.setCombatDummyPresent(combatDummy); + + if (remove) + { + chatService.removeHost(world, location, house); + } + else + { + chatService.addHost(world, location, house); + } + } + + @GetMapping("/hosts") + public House[] getHosts(@RequestParam int world, @RequestParam String location) + { + return chatService.getHosts(world, location); + } +} \ No newline at end of file diff --git a/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatService.java b/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatService.java new file mode 100644 index 0000000000..aa23d3f18d --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/chat/ChatService.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.chat; + +import java.time.Duration; +import java.util.List; +import net.runelite.http.api.chat.ChatClient; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.chat.House; +import net.runelite.http.service.util.redis.RedisPool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import redis.clients.jedis.Jedis; + +@Service +public class ChatService +{ + private static final Duration EXPIRE = Duration.ofMinutes(2); + + private final RedisPool jedisPool; + private final ChatClient chatClient = new ChatClient(); + + + @Autowired + public ChatService(RedisPool jedisPool) + { + this.jedisPool = jedisPool; + } + + public String getLayout(String name) + { + String value; + try (Jedis jedis = jedisPool.getResource()) + { + value = jedis.get("layout." + name); + } + return value; + } + + public void setLayout(String name, String layout) + { + if (!chatClient.testLayout(layout)) + { + throw new IllegalArgumentException(layout); + } + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout); + } + } + + public void addHost(int world, String location, House house) + { + String houseJSON = house.toString(); + + String key = "hosts.w" + Integer.toString(world) + "." + location; + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.rpush(key, houseJSON); + } + } + + public House[] getHosts(int world, String location) + { + List json; + String key = "hosts.w" + Integer.toString(world) + "." + location; + + try (Jedis jedis = jedisPool.getResource()) + { + json = jedis.lrange(key, 0, 25); + } + + if (json.isEmpty()) + { + return null; + } + + House[] hosts = new House[json.size()]; + for (int i = 0; i < json.size(); i++) + { + hosts[i] = RuneLiteAPI.GSON.fromJson(json.get(i), House.class); + } + return hosts; + } + + public void removeHost(int world, String location, House house) + { + String json = house.toString(); + String key = "hosts.w" + Integer.toString(world) + "." + location; + + try (Jedis jedis = jedisPool.getResource()) + { + jedis.lrem(key, 0, json); + } + } +} \ No newline at end of file diff --git a/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaCache.java b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaCache.java new file mode 100644 index 0000000000..7c5f2bb5b0 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaCache.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xtea; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +class XteaCache +{ + private int region; + private int key1; + private int key2; + private int key3; + private int key4; +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaController.java b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaController.java new file mode 100644 index 0000000000..b0c592fc70 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaController.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xtea; + +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.http.api.xtea.XteaKey; +import net.runelite.http.api.xtea.XteaRequest; +import net.runelite.http.service.util.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/xtea") +public class XteaController +{ + @Autowired + private XteaEndpoint xteaService; + + @RequestMapping(method = POST) + public void submit(@RequestBody XteaRequest xteaRequest) + { + xteaService.submit(xteaRequest); + } + + @GetMapping + public List get() + { + return xteaService.get().stream() + .map(XteaController::entryToKey) + .collect(Collectors.toList()); + } + + @GetMapping("/{region}") + public XteaKey getRegion(@PathVariable int region) + { + XteaEntry xteaRegion = xteaService.getRegion(region); + if (xteaRegion == null) + { + throw new NotFoundException(); + } + + return entryToKey(xteaRegion); + } + + private static XteaKey entryToKey(XteaEntry xe) + { + XteaKey xteaKey = new XteaKey(); + xteaKey.setRegion(xe.getRegion()); + xteaKey.setKeys(new int[] + { + xe.getKey1(), + xe.getKey2(), + xe.getKey3(), + xe.getKey4() + }); + return xteaKey; + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEndpoint.java b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEndpoint.java new file mode 100644 index 0000000000..e6c82d74ac --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEndpoint.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xtea; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.List; +import net.runelite.http.api.xtea.XteaKey; +import net.runelite.http.api.xtea.XteaRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +public class XteaEndpoint +{ + private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `region` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " `rev` int(11) NOT NULL,\n" + + " `key1` int(11) NOT NULL,\n" + + " `key2` int(11) NOT NULL,\n" + + " `key3` int(11) NOT NULL,\n" + + " `key4` int(11) NOT NULL,\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `region` (`region`,`time`)\n" + + ") ENGINE=InnoDB"; + + private final Sql2o sql2o; + + private final Cache keyCache = CacheBuilder.newBuilder() + .maximumSize(1024) + .build(); + + @Autowired + public XteaEndpoint( + @Qualifier("Runelite SQL2O") Sql2o sql2o + ) + { + this.sql2o = sql2o; + + try (Connection con = sql2o.beginTransaction()) + { + con.createQuery(CREATE_SQL) + .executeUpdate(); + } + } + + private XteaEntry findLatestXtea(Connection con, int region) + { + return con.createQuery("select region, time, key1, key2, key3, key4 from xtea " + + "where region = :region " + + "order by time desc " + + "limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + + public void submit(XteaRequest xteaRequest) + { + boolean cached = true; + for (XteaKey key : xteaRequest.getKeys()) + { + int region = key.getRegion(); + int[] keys = key.getKeys(); + + XteaCache xteaCache = keyCache.getIfPresent(region); + if (xteaCache == null + || xteaCache.getKey1() != keys[0] + || xteaCache.getKey2() != keys[1] + || xteaCache.getKey3() != keys[2] + || xteaCache.getKey4() != keys[3]) + { + cached = false; + keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3])); + } + } + + if (cached) + { + return; + } + + try (Connection con = sql2o.beginTransaction()) + { + Query query = null; + + for (XteaKey key : xteaRequest.getKeys()) + { + int region = key.getRegion(); + int[] keys = key.getKeys(); + + XteaEntry xteaEntry = findLatestXtea(con, region); + + if (keys.length != 4) + { + throw new IllegalArgumentException("Key length must be 4"); + } + + // already have these? + // TODO : check if useful / works should check with findLatestXtea + if (xteaEntry != null + && xteaEntry.getKey1() == keys[0] + && xteaEntry.getKey2() == keys[1] + && xteaEntry.getKey3() == keys[2] + && xteaEntry.getKey4() == keys[3]) + { + continue; + } + + + if (query == null) + { + query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) " + + "values (:region, :rev, :key1, :key2, :key3, :key4)"); + } + + query.addParameter("region", region) + .addParameter("rev", xteaRequest.getRevision()) + .addParameter("key1", keys[0]) + .addParameter("key2", keys[1]) + .addParameter("key3", keys[2]) + .addParameter("key4", keys[3]) + .addToBatch(); + } + + if (query != null) + { + query.executeBatch(); + con.commit(false); + } + } + } + + public List get() + { + try (Connection con = sql2o.open()) + { + return con.createQuery( + "select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " + + "(select region,max(id) as id from xtea group by region) t1 " + + "join xtea t2 on t1.id = t2.id") + .executeAndFetch(XteaEntry.class); + } + } + + public XteaEntry getRegion(int region) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea " + + "where region = :region order by time desc limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + } +} diff --git a/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEntry.java b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEntry.java new file mode 100644 index 0000000000..e1e86d0bf7 --- /dev/null +++ b/http-service-plus/src/main/java/net/runelite/http/service/xtea/XteaEntry.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xtea; + +import java.time.Instant; + +public class XteaEntry +{ + private int region; + private Instant time; + private int rev; + private int key1; + private int key2; + private int key3; + private int key4; + + public int getRegion() + { + return region; + } + + public void setRegion(int region) + { + this.region = region; + } + + public Instant getTime() + { + return time; + } + + public void setTime(Instant time) + { + this.time = time; + } + + public int getRev() + { + return rev; + } + + public void setRev(int rev) + { + this.rev = rev; + } + + public int getKey1() + { + return key1; + } + + public void setKey1(int key1) + { + this.key1 = key1; + } + + public int getKey2() + { + return key2; + } + + public void setKey2(int key2) + { + this.key2 = key2; + } + + public int getKey3() + { + return key3; + } + + public void setKey3(int key3) + { + this.key3 = key3; + } + + public int getKey4() + { + return key4; + } + + public void setKey4(int key4) + { + this.key4 = key4; + } + +} diff --git a/http-service-plus/src/main/resources/application-dev.yaml b/http-service-plus/src/main/resources/application-dev.yaml new file mode 100644 index 0000000000..cc2286e9b3 --- /dev/null +++ b/http-service-plus/src/main/resources/application-dev.yaml @@ -0,0 +1,31 @@ +# Enable debug logging +debug: true +logging.level.net.runelite: DEBUG + +# Development data sources +datasource: + runelite: + jndiName: + driverClassName: org.mariadb.jdbc.Driver + type: org.mariadb.jdbc.MariaDbDataSource + url: jdbc:mariadb://localhost:3306/runelite + username: runelite + password: runelite + runelite-cache: + jndiName: + driverClassName: org.mariadb.jdbc.Driver + type: org.mariadb.jdbc.MariaDbDataSource + url: jdbc:mariadb://localhost:3306/cache + username: runelite + password: runelite + runelite-tracker: + jndiName: + driverClassName: org.mariadb.jdbc.Driver + type: org.mariadb.jdbc.MariaDbDataSource + url: jdbc:mariadb://localhost:3306/xptracker + username: runelite + password: runelite + +# Development oauth callback (without proxy) +oauth: + callback: http://localhost:8080/account/callback diff --git a/http-service-plus/src/main/resources/application.yaml b/http-service-plus/src/main/resources/application.yaml new file mode 100644 index 0000000000..8e46babb83 --- /dev/null +++ b/http-service-plus/src/main/resources/application.yaml @@ -0,0 +1,39 @@ +datasource: + runeliteplus: + jndiName: java:comp/env/jdbc/runelite + runeliteplus-cache: + jndiName: java:comp/env/jdbc/runelite-cache2 + runeliteplus-tracker: + jndiName: java:comp/env/jdbc/runelite-tracker + +# By default Spring tries to register the datasource as an MXBean, +# so if multiple apis are deployed on one web container with +# shared datasource it tries to register it multiples times and +# fails when starting the 2nd api +spring.jmx.enabled: false + +# Google OAuth client +oauth: + client-id: + client-secret: + callback: https://api.runelite.net/oauth/ + +# Minio client storage for cache +minio: + endpoint: http://localhost:9000 + accesskey: AM54M27O4WZK65N6F8IP + secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP + bucket: runelite + +# Redis client for temporary data storage +redis: + pool.size: 10 + host: http://localhost:6379 + + +# Twitter client for feed +runelite: + twitter: + consumerkey: + secretkey: + listid: 968949795153948673 \ No newline at end of file diff --git a/http-service-plus/src/main/resources/net/runelite/http/service/xp/schema.sql b/http-service-plus/src/main/resources/net/runelite/http/service/xp/schema.sql new file mode 100644 index 0000000000..70f83a30fd --- /dev/null +++ b/http-service-plus/src/main/resources/net/runelite/http/service/xp/schema.sql @@ -0,0 +1,135 @@ +-- MySQL dump 10.16 Distrib 10.2.18-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: xptracker +-- ------------------------------------------------------ +-- Server version 10.2.18-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `player` +-- + +DROP TABLE IF EXISTS `player`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(32) NOT NULL, + `tracked_since` timestamp NOT NULL DEFAULT current_timestamp(), + `last_updated` timestamp NOT NULL DEFAULT current_timestamp(), + `rank` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `xp` +-- + +DROP TABLE IF EXISTS `xp`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `xp` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NOT NULL DEFAULT current_timestamp(), + `player` int(11) NOT NULL, + `attack_xp` int(11) NOT NULL, + `defence_xp` int(11) NOT NULL, + `strength_xp` int(11) NOT NULL, + `hitpoints_xp` int(11) NOT NULL, + `ranged_xp` int(11) NOT NULL, + `prayer_xp` int(11) NOT NULL, + `magic_xp` int(11) NOT NULL, + `cooking_xp` int(11) NOT NULL, + `woodcutting_xp` int(11) NOT NULL, + `fletching_xp` int(11) NOT NULL, + `fishing_xp` int(11) NOT NULL, + `firemaking_xp` int(11) NOT NULL, + `crafting_xp` int(11) NOT NULL, + `smithing_xp` int(11) NOT NULL, + `mining_xp` int(11) NOT NULL, + `herblore_xp` int(11) NOT NULL, + `agility_xp` int(11) NOT NULL, + `thieving_xp` int(11) NOT NULL, + `slayer_xp` int(11) NOT NULL, + `farming_xp` int(11) NOT NULL, + `runecraft_xp` int(11) NOT NULL, + `hunter_xp` int(11) NOT NULL, + `construction_xp` int(11) NOT NULL, + `overall_xp` int(11) GENERATED ALWAYS AS (`attack_xp` + `defence_xp` + `strength_xp` + `hitpoints_xp` + `ranged_xp` + `prayer_xp` + `magic_xp` + `cooking_xp` + `woodcutting_xp` + `fletching_xp` + `fishing_xp` + `firemaking_xp` + `crafting_xp` + `smithing_xp` + `mining_xp` + `herblore_xp` + `agility_xp` + `thieving_xp` + `slayer_xp` + `farming_xp` + `runecraft_xp` + `hunter_xp` + `construction_xp`) VIRTUAL, + `attack_level` int(11) GENERATED ALWAYS AS (level_for_xp(`attack_xp` AS `attack_xp`)) VIRTUAL, + `defence_level` int(11) GENERATED ALWAYS AS (level_for_xp(`defence_xp` AS `defence_xp`)) VIRTUAL, + `strength_level` int(11) GENERATED ALWAYS AS (level_for_xp(`strength_xp` AS `strength_xp`)) VIRTUAL, + `hitpoints_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hitpoints_xp` AS `hitpoints_xp`)) VIRTUAL, + `ranged_level` int(11) GENERATED ALWAYS AS (level_for_xp(`ranged_xp` AS `ranged_xp`)) VIRTUAL, + `prayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`prayer_xp` AS `prayer_xp`)) VIRTUAL, + `magic_level` int(11) GENERATED ALWAYS AS (level_for_xp(`magic_xp` AS `magic_xp`)) VIRTUAL, + `cooking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`cooking_xp` AS `cooking_xp`)) VIRTUAL, + `woodcutting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`woodcutting_xp` AS `woodcutting_xp`)) VIRTUAL, + `fletching_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fletching_xp` AS `fletching_xp`)) VIRTUAL, + `fishing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`fishing_xp` AS `fishing_xp`)) VIRTUAL, + `firemaking_level` int(11) GENERATED ALWAYS AS (level_for_xp(`firemaking_xp` AS `firemaking_xp`)) VIRTUAL, + `crafting_level` int(11) GENERATED ALWAYS AS (level_for_xp(`crafting_xp` AS `crafting_xp`)) VIRTUAL, + `smithing_level` int(11) GENERATED ALWAYS AS (level_for_xp(`smithing_xp` AS `smithing_xp`)) VIRTUAL, + `mining_level` int(11) GENERATED ALWAYS AS (level_for_xp(`mining_xp` AS `mining_xp`)) VIRTUAL, + `herblore_level` int(11) GENERATED ALWAYS AS (level_for_xp(`herblore_xp` AS `herblore_xp`)) VIRTUAL, + `agility_level` int(11) GENERATED ALWAYS AS (level_for_xp(`agility_xp` AS `agility_xp`)) VIRTUAL, + `thieving_level` int(11) GENERATED ALWAYS AS (level_for_xp(`thieving_xp` AS `thieving_xp`)) VIRTUAL, + `slayer_level` int(11) GENERATED ALWAYS AS (level_for_xp(`slayer_xp` AS `slayer_xp`)) VIRTUAL, + `farming_level` int(11) GENERATED ALWAYS AS (level_for_xp(`farming_xp` AS `farming_xp`)) VIRTUAL, + `runecraft_level` int(11) GENERATED ALWAYS AS (level_for_xp(`runecraft_xp` AS `runecraft_xp`)) VIRTUAL, + `hunter_level` int(11) GENERATED ALWAYS AS (level_for_xp(`hunter_xp` AS `hunter_xp`)) VIRTUAL, + `construction_level` int(11) GENERATED ALWAYS AS (level_for_xp(`construction_xp` AS `construction_xp`)) VIRTUAL, + `overall_level` int(11) GENERATED ALWAYS AS (`attack_level` + `defence_level` + `strength_level` + `hitpoints_level` + `ranged_level` + `prayer_level` + `magic_level` + `cooking_level` + `woodcutting_level` + `fletching_level` + `fishing_level` + `firemaking_level` + `crafting_level` + `smithing_level` + `mining_level` + `herblore_level` + `agility_level` + `thieving_level` + `slayer_level` + `farming_level` + `runecraft_level` + `hunter_level` + `construction_level`) VIRTUAL, + `attack_rank` int(11) NOT NULL, + `defence_rank` int(11) NOT NULL, + `strength_rank` int(11) NOT NULL, + `hitpoints_rank` int(11) NOT NULL, + `ranged_rank` int(11) NOT NULL, + `prayer_rank` int(11) NOT NULL, + `magic_rank` int(11) NOT NULL, + `cooking_rank` int(11) NOT NULL, + `woodcutting_rank` int(11) NOT NULL, + `fletching_rank` int(11) NOT NULL, + `fishing_rank` int(11) NOT NULL, + `firemaking_rank` int(11) NOT NULL, + `crafting_rank` int(11) NOT NULL, + `smithing_rank` int(11) NOT NULL, + `mining_rank` int(11) NOT NULL, + `herblore_rank` int(11) NOT NULL, + `agility_rank` int(11) NOT NULL, + `thieving_rank` int(11) NOT NULL, + `slayer_rank` int(11) NOT NULL, + `farming_rank` int(11) NOT NULL, + `runecraft_rank` int(11) NOT NULL, + `hunter_rank` int(11) NOT NULL, + `construction_rank` int(11) NOT NULL, + `overall_rank` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `player_time` (`player`,`time`), + KEY `idx_time` (`time`), + CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2019-02-15 21:01:17 \ No newline at end of file diff --git a/http-service-plus/src/main/templates/markdown.hbs b/http-service-plus/src/main/templates/markdown.hbs new file mode 100644 index 0000000000..1beacd052a --- /dev/null +++ b/http-service-plus/src/main/templates/markdown.hbs @@ -0,0 +1,110 @@ +{{#info}} +# {{title}} +{{join schemes " | "}}://{{host}}{{basePath}} + +{{description}} + +{{#contact}} +[**Contact the developer**](mailto:{{email}}) +{{/contact}} + +**Version** {{version}} + +{{#if termsOfService}} +[**Terms of Service**]({{termsOfService}}) +{{/if}} + +{{/info}} + +{{#if consumes}}__Consumes:__ {{join consumes ", "}}{{/if}} + +{{#if produces}}__Produces:__ {{join produces ", "}}{{/if}} + +{{#if securityDefinitions}} +# Security Definitions +{{> security}} +{{/if}} + +
+Table Of Contents +[toc] +
+ +# APIs + +{{#each paths}} +## {{@key}} +{{#this}} +{{#get}} +### GET +{{> operation}} +{{/get}} + +{{#put}} +### PUT +{{> operation}} +{{/put}} + +{{#post}} +### POST + +{{> operation}} + +{{/post}} + +{{#delete}} +### DELETE +{{> operation}} +{{/delete}} + +{{#option}} +### OPTION +{{> operation}} +{{/option}} + +{{#patch}} +### PATCH +{{> operation}} +{{/patch}} + +{{#head}} +### HEAD +{{> operation}} +{{/head}} + +{{/this}} +{{/each}} + +# Definitions +{{#each definitions}} +## {{@key}} + + + + + + + + + + {{#each this.properties}} + + + + + + + + {{/each}} +
nametyperequireddescriptionexample
{{@key}} + {{#ifeq type "array"}} + {{#items.$ref}} + {{type}}[{{basename items.$ref}}] + {{/items.$ref}} + {{^items.$ref}}{{type}}[{{items.type}}]{{/items.$ref}} + {{else}} + {{#$ref}}{{basename $ref}}{{/$ref}} + {{^$ref}}{{type}}{{#format}} ({{format}}){{/format}}{{/$ref}} + {{/ifeq}} + {{#required}}required{{/required}}{{^required}}optional{{/required}}{{#description}}{{{description}}}{{/description}}{{^description}}-{{/description}}{{example}}
+{{/each}} diff --git a/http-service-plus/src/main/templates/operation.hbs b/http-service-plus/src/main/templates/operation.hbs new file mode 100644 index 0000000000..f7015850b8 --- /dev/null +++ b/http-service-plus/src/main/templates/operation.hbs @@ -0,0 +1,71 @@ +{{#deprecated}}-deprecated-{{/deprecated}} +{{summary}} + +{{description}} + +{{#if externalDocs.url}}{{externalDocs.description}}. [See external documents for more details]({{externalDocs.url}}) +{{/if}} + +{{#if security}} +#### Security +{{/if}} + +{{#security}} +{{#each this}} +* {{@key}} +{{#this}} * {{this}} +{{/this}} +{{/each}} +{{/security}} + +#### Request + +{{#if consumes}}__Content-Type:__ {{join consumes ", "}}{{/if}} + +##### Parameters +{{#if parameters}} + + + + + + + + + +{{/if}} + +{{#parameters}} + + + + + + +{{#ifeq in "body"}} + +{{else}} + {{#ifeq type "array"}} + + {{else}} + + {{/ifeq}} +{{/ifeq}} + +{{/parameters}} +{{#if parameters}} +
NameLocated inRequiredDescriptionDefaultSchema
{{name}}{{in}}{{#if required}}yes{{else}}no{{/if}}{{description}}{{#if pattern}} (**Pattern**: `{{pattern}}`){{/if}} - + {{#ifeq schema.type "array"}}Array[{{basename schema.items.$ref}}]{{/ifeq}} + {{#schema.$ref}}{{basename schema.$ref}} {{/schema.$ref}} + Array[{{items.type}}] ({{collectionFormat}}){{type}} {{#format}}({{format}}){{/format}}
+{{/if}} + + +#### Response + +{{#if produces}}__Content-Type:__ {{join produces ", "}}{{/if}} + +| Status Code | Reason | Response Model | +|-------------|-------------|----------------| +{{#each responses}}| {{@key}} | {{description}} | {{#schema.$ref}}{{basename schema.$ref}}{{/schema.$ref}}{{#ifeq schema.type "array"}}Array[{{basename schema.items.$ref}}]{{/ifeq}}{{^schema}} - {{/schema}}| +{{/each}} diff --git a/http-service-plus/src/main/templates/security.hbs b/http-service-plus/src/main/templates/security.hbs new file mode 100644 index 0000000000..04f86e8380 --- /dev/null +++ b/http-service-plus/src/main/templates/security.hbs @@ -0,0 +1,88 @@ +{{#each securityDefinitions}} +### {{@key}} +{{#this}} +{{#ifeq type "oauth2"}} + + + + + +{{#if description}} + + + + +{{/if}} +{{#if authorizationUrl}} + + + + +{{/if}} +{{#if flow}} + + + + +{{/if}} +{{#if tokenUrl}} + + + + +{{/if}} +{{#if scopes}} + + +{{#each scopes}} + + + + +{{/each}} + +{{/if}} +
type{{type}}
description{{description}}
authorizationUrl{{authorizationUrl}}
flow{{flow}}
tokenUrl{{tokenUrl}}
scopes{{@key}}{{this}}
+{{/ifeq}} +{{#ifeq type "apiKey"}} + + + + + +{{#if description}} + + + + +{{/if}} +{{#if name}} + + + + +{{/if}} +{{#if in}} + + + + +{{/if}} +
type{{type}}
description{{description}}
name{{name}}
in{{in}}
+{{/ifeq}} +{{#ifeq type "basic"}} + + + + + +{{#if description}} + + + + +{{/if}} +
type{{type}}
description{{description}}
+{{/ifeq}} +{{/this}} +{{/each}} \ No newline at end of file diff --git a/http-service-plus/src/main/templates/template.html.hbs b/http-service-plus/src/main/templates/template.html.hbs new file mode 100644 index 0000000000..da587c2cc4 --- /dev/null +++ b/http-service-plus/src/main/templates/template.html.hbs @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + {{info.title}} {{info.version}} + + + + + \ No newline at end of file diff --git a/scripts/pom.xml b/http-service-plus/src/main/webapp/WEB-INF/web.xml similarity index 68% rename from scripts/pom.xml rename to http-service-plus/src/main/webapp/WEB-INF/web.xml index 7898c2e8fb..0b07dbb973 100644 --- a/scripts/pom.xml +++ b/http-service-plus/src/main/webapp/WEB-INF/web.xml @@ -1,4 +1,3 @@ - - - 4.0.0 + - net.runelite - scripts - 1.0.0 - Scripts - - - - - org.apache.maven.wagon - wagon-webdav-jackrabbit - 2.12 - - - - + RuneLite API + \ No newline at end of file diff --git a/http-service-plus/src/test/resources/application-test.yaml b/http-service-plus/src/test/resources/application-test.yaml new file mode 100644 index 0000000000..0532963ade --- /dev/null +++ b/http-service-plus/src/test/resources/application-test.yaml @@ -0,0 +1,17 @@ +# Use in-memory database for tests +datasource: + runelite: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:runelite + runelite-cache: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:cache + runelite-tracker: + jndiName: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:xptracker diff --git a/http-service-plus/src/test/resources/net/runelite/http/service/worlds/worldlist b/http-service-plus/src/test/resources/net/runelite/http/service/worlds/worldlist new file mode 100644 index 0000000000..1d1360e579 Binary files /dev/null and b/http-service-plus/src/test/resources/net/runelite/http/service/worlds/worldlist differ diff --git a/http-service/build.gradle b/http-service/build.gradle new file mode 100644 index 0000000000..d982af2e73 --- /dev/null +++ b/http-service/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'war' + +description = 'Web Service' + +dependencies { + annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: mapstruct + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok + + api project(':cache') + api project(':http-api') + api project(':runelite-api') + + implementation group: 'com.github.scribejava', name: 'scribejava-apis', version: scribejava + implementation group: 'com.google.code.gson', name: 'gson', version: gson + implementation group: 'com.google.guava', name: 'guava', version: guava + implementation group: 'io.minio', name: 'minio', version: minio + implementation group: 'org.mapstruct', name: 'mapstruct-jdk8', version: mapstruct + implementation group: 'org.mongodb', name: 'mongodb-driver-sync', version: mongodbDriverSync + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4j + implementation group: 'org.springframework', name: 'spring-jdbc', version: springJdbc + implementation group: 'org.springframework.boot', name: 'spring-boot-devtools', version: springboot + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springboot + implementation group: 'org.sql2o', name: 'sql2o', version: sql2o + implementation(group: 'redis.clients', name: 'jedis', version: jedis) { + exclude(module: 'commons-pool2') + } + + providedCompile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: mariadbJdbc + providedCompile group: 'org.projectlombok', name: 'lombok', version: lombok + providedCompile group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat', version: springboot + + testImplementation group: 'com.h2database', name: 'h2', version: '1.4.199' + testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: okhttp3 + testImplementation(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springboot) { + exclude(module: 'commons-logging') + } +} diff --git a/http-service/pom.xml b/http-service/pom.xml deleted file mode 100644 index cb962d7c95..0000000000 --- a/http-service/pom.xml +++ /dev/null @@ -1,241 +0,0 @@ - - - - 4.0.0 - - net.runelite - runelite-parent - 1.5.32-SNAPSHOT - - - Web Service - http-service - war - - - 1.5.6.RELEASE - 1.2.0.Final - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - org.springframework.boot - spring-boot-devtools - true - - - org.springframework - spring-jdbc - - - - org.mapstruct - mapstruct-jdk8 - ${mapstruct.version} - - - org.projectlombok - lombok - provided - - - - net.runelite - http-api - ${project.version} - - - net.runelite - cache - ${project.version} - - - - org.mariadb.jdbc - mariadb-java-client - 2.2.3 - provided - - - org.sql2o - sql2o - 1.5.4 - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - com.github.scribejava - scribejava-apis - 4.1.0 - - - io.minio - minio - 3.0.6 - - - redis.clients - jedis - 2.10.0 - - - org.apache.commons - commons-pool2 - - - - - org.mongodb - mongodb-driver-sync - 3.10.2 - provided - - - - org.springframework.boot - spring-boot-starter-test - test - - - com.squareup.okhttp3 - mockwebserver - 3.7.0 - test - - - com.h2database - h2 - test - - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - - - - runelite-${project.version} - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - org.projectlombok - lombok - ${lombok.version} - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - com.github.kongchen - swagger-maven-plugin - 3.1.8 - - - - javax.xml.bind - jaxb-api - 2.3.1 - - - - - - true - - net.runelite - - - https - - api.runelite.net - /runelite-${project.version} - - ${project.parent.name} HTTP API - ${project.version} - ${project.description} - - https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) - BSD 2-Clause "Simplified" - - - ${basedir}/src/main/templates/template.html.hbs - ${project.build.directory}/swagger-ui - ${project.build.directory}/site/api.html - true - json - - - - - - compile - - generate - - - - - - - diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java index 177561cf59..3c4042e981 100644 --- a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java +++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -53,7 +53,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.jndi.JndiTemplate; diff --git a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java index 4f39ed00e1..19add15121 100644 --- a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java +++ b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java @@ -145,18 +145,19 @@ public class AccountService state.setUuid(uuid); state.setApiVersion(RuneLiteAPI.getVersion()); - OAuth20Service service = new ServiceBuilder() - .apiKey(oauthClientId) + OAuth20Service service = new ServiceBuilder(oauthClientId) .apiSecret(oauthClientSecret) - .scope(SCOPE) + .defaultScope(SCOPE) .callback(oauthCallback) - .state(gson.toJson(state)) .build(GoogleApi20.instance()); final Map additionalParams = new HashMap<>(); additionalParams.put("prompt", "select_account"); - String authorizationUrl = service.getAuthorizationUrl(additionalParams); + final String authorizationUrl = service.createAuthorizationUrlBuilder() + .state(gson.toJson(state)) + .additionalParams(additionalParams) + .build(); OAuthResponse lr = new OAuthResponse(); lr.setOauthUrl(authorizationUrl); @@ -184,12 +185,10 @@ public class AccountService logger.info("Got authorization code {} for uuid {}", code, state.getUuid()); - OAuth20Service service = new ServiceBuilder() - .apiKey(oauthClientId) + OAuth20Service service = new ServiceBuilder(oauthClientId) .apiSecret(oauthClientSecret) - .scope(SCOPE) + .defaultScope(SCOPE) .callback(oauthCallback) - .state(gson.toJson(state)) .build(GoogleApi20.instance()); OAuth2AccessToken accessToken = service.getAccessToken(code); diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java index c0b767a68c..f058510765 100644 --- a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java @@ -35,6 +35,7 @@ import io.minio.errors.InvalidArgumentException; import io.minio.errors.InvalidBucketNameException; import io.minio.errors.InvalidEndpointException; import io.minio.errors.InvalidPortException; +import io.minio.errors.InvalidResponseException; import io.minio.errors.NoResponseException; import java.io.IOException; import java.io.InputStream; @@ -113,9 +114,7 @@ public class CacheService { return ByteStreams.toByteArray(in); } - catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException - | IOException | InvalidKeyException | NoResponseException | XmlPullParserException - | ErrorResponseException | InternalException | InvalidArgumentException ex) + catch (InvalidBucketNameException | NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException | InternalException | InvalidArgumentException | InvalidResponseException ex) { log.warn(null, ex); return null; diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java index 971094a585..51d3f485f0 100644 --- a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java @@ -1,109 +1,109 @@ -/* - * Copyright (c) 2019, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.config; - -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import net.runelite.http.api.config.Configuration; -import net.runelite.http.service.account.AuthFilter; -import net.runelite.http.service.account.beans.SessionEntry; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.DELETE; -import static org.springframework.web.bind.annotation.RequestMethod.PUT; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/config") -public class ConfigController -{ - private final ConfigService configService; - private final AuthFilter authFilter; - - @Autowired - public ConfigController(ConfigService configService, AuthFilter authFilter) - { - this.configService = configService; - this.authFilter = authFilter; - } - - @GetMapping - public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException - { - SessionEntry session = authFilter.handle(request, response); - - if (session == null) - { - return null; - } - - return configService.get(session.getUser()); - } - - @RequestMapping(path = "/{key:.+}", method = PUT) - public void setKey( - HttpServletRequest request, - HttpServletResponse response, - @PathVariable String key, - @RequestBody(required = false) String value - ) throws IOException - { - SessionEntry session = authFilter.handle(request, response); - - if (session == null) - { - return; - } - - if (!configService.setKey(session.getUser(), key, value)) - { - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - - @RequestMapping(path = "/{key:.+}", method = DELETE) - public void unsetKey( - HttpServletRequest request, - HttpServletResponse response, - @PathVariable String key - ) throws IOException - { - SessionEntry session = authFilter.handle(request, response); - - if (session == null) - { - return; - } - - if (!configService.unsetKey(session.getUser(), key)) - { - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } -} +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.config.Configuration; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/config") +public class ConfigController +{ + private final ConfigService configService; + private final AuthFilter authFilter; + + @Autowired + public ConfigController(ConfigService configService, AuthFilter authFilter) + { + this.configService = configService; + this.authFilter = authFilter; + } + + @GetMapping + public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return null; + } + + return configService.get(session.getUser()); + } + + @RequestMapping(path = "/{key:.+}", method = PUT) + public void setKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key, + @RequestBody(required = false) String value + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + if (!configService.setKey(session.getUser(), key, value)) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @RequestMapping(path = "/{key:.+}", method = DELETE) + public void unsetKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + if (!configService.unsetKey(session.getUser(), key)) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java index 80d8f738ba..5afc6299bc 100644 --- a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java +++ b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java @@ -1,73 +1,73 @@ -/* - * Copyright (c) 2018, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.sprite; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/sprite") -public class SpriteController -{ - @Autowired - private SpriteService spriteService; - - private final LoadingCache spriteCache = CacheBuilder.newBuilder() - .maximumSize(1024L) - .expireAfterWrite(10, TimeUnit.MINUTES) - .build(new CacheLoader() - { - @Override - public byte[] load(Integer key) throws Exception - { - byte[] data = spriteService.getImagePng(key >>> 16, key & 0xffff); - return data != null ? data : new byte[0]; - } - }); - - @GetMapping(produces = "image/png") - public ResponseEntity getSprite( - @RequestParam int spriteId, - @RequestParam(defaultValue = "0") int frameId - ) throws IOException - { - byte[] data = spriteCache.getUnchecked(spriteId << 16 | frameId); - if (data == null || data.length == 0) - { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(data); - } -} +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.sprite; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/sprite") +public class SpriteController +{ + @Autowired + private SpriteService spriteService; + + private final LoadingCache spriteCache = CacheBuilder.newBuilder() + .maximumSize(1024L) + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(new CacheLoader() + { + @Override + public byte[] load(Integer key) throws Exception + { + byte[] data = spriteService.getImagePng(key >>> 16, key & 0xffff); + return data != null ? data : new byte[0]; + } + }); + + @GetMapping(produces = "image/png") + public ResponseEntity getSprite( + @RequestParam int spriteId, + @RequestParam(defaultValue = "0") int frameId + ) throws IOException + { + byte[] data = spriteCache.getUnchecked(spriteId << 16 | frameId); + if (data == null || data.length == 0) + { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(data); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java index 3a3fa015aa..659f1e03e2 100644 --- a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java +++ b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java @@ -1,117 +1,117 @@ -/* - * Copyright (c) 2018, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.sprite; - -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import javax.imageio.ImageIO; -import net.runelite.cache.IndexType; -import net.runelite.cache.definitions.SpriteDefinition; -import net.runelite.cache.definitions.loaders.SpriteLoader; -import net.runelite.cache.fs.ArchiveFiles; -import net.runelite.cache.fs.FSFile; -import net.runelite.http.service.cache.CacheService; -import net.runelite.http.service.cache.beans.ArchiveEntry; -import net.runelite.http.service.cache.beans.CacheEntry; -import net.runelite.http.service.cache.beans.IndexEntry; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class SpriteService -{ - @Autowired - private CacheService cacheService; - - public SpriteDefinition getSprite(int spriteId, int frameId) throws IOException - { - CacheEntry cache = cacheService.findMostRecent(); - if (cache == null) - { - return null; - } - - IndexEntry index = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber()); - if (index == null) - { - return null; - } - - ArchiveEntry archive = cacheService.findArchiveForIndex(index, spriteId); - if (archive == null) - { - return null; - } - - ArchiveFiles files = cacheService.getArchiveFiles(archive); - if (files == null) - { - return null; - } - - FSFile file = files.getFiles().get(0); - byte[] contents = file.getContents(); - SpriteDefinition[] sprite = new SpriteLoader().load(archive.getArchiveId(), contents); - if (frameId < 0 || frameId >= sprite.length) - { - return null; - } - - return sprite[frameId]; - } - - public BufferedImage getImage(int spriteId, int frameId) throws IOException - { - SpriteDefinition sprite = getSprite(spriteId, frameId); - if (sprite == null) - { - return null; - } - - BufferedImage bufferedImage = getSpriteImage(sprite); - return bufferedImage; - } - - public byte[] getImagePng(int spriteId, int frameId) throws IOException - { - BufferedImage image = getImage(spriteId, frameId); - if (image == null) - { - return null; - } - - ByteArrayOutputStream bao = new ByteArrayOutputStream(); - ImageIO.write(image, "png", bao); - return bao.toByteArray(); - } - - private BufferedImage getSpriteImage(SpriteDefinition sprite) - { - BufferedImage image = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB); - image.setRGB(0, 0, sprite.getWidth(), sprite.getHeight(), sprite.getPixels(), 0, sprite.getWidth()); - return image; - } -} +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.sprite; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import javax.imageio.ImageIO; +import net.runelite.cache.IndexType; +import net.runelite.cache.definitions.SpriteDefinition; +import net.runelite.cache.definitions.loaders.SpriteLoader; +import net.runelite.cache.fs.ArchiveFiles; +import net.runelite.cache.fs.FSFile; +import net.runelite.http.service.cache.CacheService; +import net.runelite.http.service.cache.beans.ArchiveEntry; +import net.runelite.http.service.cache.beans.CacheEntry; +import net.runelite.http.service.cache.beans.IndexEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class SpriteService +{ + @Autowired + private CacheService cacheService; + + public SpriteDefinition getSprite(int spriteId, int frameId) throws IOException + { + CacheEntry cache = cacheService.findMostRecent(); + if (cache == null) + { + return null; + } + + IndexEntry index = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber()); + if (index == null) + { + return null; + } + + ArchiveEntry archive = cacheService.findArchiveForIndex(index, spriteId); + if (archive == null) + { + return null; + } + + ArchiveFiles files = cacheService.getArchiveFiles(archive); + if (files == null) + { + return null; + } + + FSFile file = files.getFiles().get(0); + byte[] contents = file.getContents(); + SpriteDefinition[] sprite = new SpriteLoader().load(archive.getArchiveId(), contents); + if (frameId < 0 || frameId >= sprite.length) + { + return null; + } + + return sprite[frameId]; + } + + public BufferedImage getImage(int spriteId, int frameId) throws IOException + { + SpriteDefinition sprite = getSprite(spriteId, frameId); + if (sprite == null) + { + return null; + } + + BufferedImage bufferedImage = getSpriteImage(sprite); + return bufferedImage; + } + + public byte[] getImagePng(int spriteId, int frameId) throws IOException + { + BufferedImage image = getImage(spriteId, frameId); + if (image == null) + { + return null; + } + + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + ImageIO.write(image, "png", bao); + return bao.toByteArray(); + } + + private BufferedImage getSpriteImage(SpriteDefinition sprite) + { + BufferedImage image = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB); + image.setRGB(0, 0, sprite.getWidth(), sprite.getHeight(), sprite.getPixels(), 0, sprite.getWidth()); + return image; + } +} diff --git a/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java index 27320f3f72..646b19611e 100644 --- a/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java +++ b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java @@ -1,85 +1,85 @@ -/* - * Copyright (c) 2019, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.config; - -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import net.runelite.http.service.account.AuthFilter; -import net.runelite.http.service.account.beans.SessionEntry; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@RunWith(SpringRunner.class) -@WebMvcTest(ConfigController.class) -@ActiveProfiles("test") -public class ConfigControllerTest -{ - @Autowired - private MockMvc mockMvc; - - @MockBean - private ConfigService configService; - - @MockBean - private AuthFilter authFilter; - - @Before - public void before() throws IOException - { - when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) - .thenReturn(mock(SessionEntry.class)); - - when(configService.setKey(anyInt(), anyString(), anyString())).thenReturn(true); - } - - @Test - public void testSetKey() throws Exception - { - mockMvc.perform(put("/config/key") - .content("value") - .contentType(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()); - - verify(configService).setKey(anyInt(), eq("key"), eq("value")); - } +/* + * Copyright (c) 2019, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(ConfigController.class) +@ActiveProfiles("test") +public class ConfigControllerTest +{ + @Autowired + private MockMvc mockMvc; + + @MockBean + private ConfigService configService; + + @MockBean + private AuthFilter authFilter; + + @Before + public void before() throws IOException + { + when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(mock(SessionEntry.class)); + + when(configService.setKey(anyInt(), anyString(), anyString())).thenReturn(true); + } + + @Test + public void testSetKey() throws Exception + { + mockMvc.perform(put("/config/key") + .content("value") + .contentType(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()); + + verify(configService).setKey(anyInt(), eq("key"), eq("value")); + } } \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java index 9cda0a54ed..4224205c91 100644 --- a/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java +++ b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java @@ -1,110 +1,110 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.hiscore; - -import java.io.IOException; -import net.runelite.http.api.hiscore.HiscoreEndpoint; -import net.runelite.http.api.hiscore.HiscoreResult; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class HiscoreServiceTest -{ - private static final String RESPONSE = "654683,705,1304518\n" - + "679419,50,107181\n" - + "550667,48,85764\n" - + "861497,50,101366\n" - + "891591,48,87843\n" - + "-1,1,4\n" - + "840255,27,10073\n" - + "1371912,10,1310\n" - + "432193,56,199795\n" - + "495638,56,198304\n" - + "514466,37,27502\n" - + "456981,54,159727\n" - + "459159,49,93010\n" - + "1028855,8,823\n" - + "862906,29,12749\n" - + "795020,31,16097\n" - + "673591,5,495\n" - + "352676,51,112259\n" - + "428419,40,37235\n" - + "461887,43,51971\n" - + "598582,1,10\n" - + "638177,1,0\n" - + "516239,9,1000\n" - + "492790,1,0\n" - + "-1,-1\n" - + "73,1738\n" - + "-1,-1\n" - + "531,1432\n" - + "324,212\n" - + "8008,131\n" - + "1337,911\n" - + "42,14113\n" - + "1,777\n" - + "254,92\n"; - - private final MockWebServer server = new MockWebServer(); - - @Before - public void before() throws IOException - { - server.enqueue(new MockResponse().setBody(RESPONSE)); - - server.start(); - } - - @After - public void after() throws IOException - { - server.shutdown(); - } - - @Test - public void testNormalLookup() throws Exception - { - HiscoreTestService hiscores = new HiscoreTestService(server.url("/")); - - HiscoreResult result = hiscores.lookupUsername("zezima", HiscoreEndpoint.NORMAL.getHiscoreURL()); - - Assert.assertEquals(50, result.getAttack().getLevel()); - Assert.assertEquals(159727L, result.getFishing().getExperience()); - Assert.assertEquals(492790, result.getConstruction().getRank()); - Assert.assertEquals(1432, result.getClueScrollAll().getLevel()); - Assert.assertEquals(324, result.getClueScrollBeginner().getRank()); - Assert.assertEquals(8008, result.getClueScrollEasy().getRank()); - Assert.assertEquals(911, result.getClueScrollMedium().getLevel()); - Assert.assertEquals(42, result.getClueScrollHard().getRank()); - Assert.assertEquals(777, result.getClueScrollElite().getLevel()); - Assert.assertEquals(254, result.getClueScrollMaster().getRank()); - Assert.assertEquals(-1, result.getLastManStanding().getLevel()); - } - -} +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.hiscore; + +import java.io.IOException; +import net.runelite.http.api.hiscore.HiscoreEndpoint; +import net.runelite.http.api.hiscore.HiscoreResult; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class HiscoreServiceTest +{ + private static final String RESPONSE = "654683,705,1304518\n" + + "679419,50,107181\n" + + "550667,48,85764\n" + + "861497,50,101366\n" + + "891591,48,87843\n" + + "-1,1,4\n" + + "840255,27,10073\n" + + "1371912,10,1310\n" + + "432193,56,199795\n" + + "495638,56,198304\n" + + "514466,37,27502\n" + + "456981,54,159727\n" + + "459159,49,93010\n" + + "1028855,8,823\n" + + "862906,29,12749\n" + + "795020,31,16097\n" + + "673591,5,495\n" + + "352676,51,112259\n" + + "428419,40,37235\n" + + "461887,43,51971\n" + + "598582,1,10\n" + + "638177,1,0\n" + + "516239,9,1000\n" + + "492790,1,0\n" + + "-1,-1\n" + + "73,1738\n" + + "-1,-1\n" + + "531,1432\n" + + "324,212\n" + + "8008,131\n" + + "1337,911\n" + + "42,14113\n" + + "1,777\n" + + "254,92\n"; + + private final MockWebServer server = new MockWebServer(); + + @Before + public void before() throws IOException + { + server.enqueue(new MockResponse().setBody(RESPONSE)); + + server.start(); + } + + @After + public void after() throws IOException + { + server.shutdown(); + } + + @Test + public void testNormalLookup() throws Exception + { + HiscoreTestService hiscores = new HiscoreTestService(server.url("/")); + + HiscoreResult result = hiscores.lookupUsername("zezima", HiscoreEndpoint.NORMAL.getHiscoreURL()); + + Assert.assertEquals(50, result.getAttack().getLevel()); + Assert.assertEquals(159727L, result.getFishing().getExperience()); + Assert.assertEquals(492790, result.getConstruction().getRank()); + Assert.assertEquals(1432, result.getClueScrollAll().getLevel()); + Assert.assertEquals(324, result.getClueScrollBeginner().getRank()); + Assert.assertEquals(8008, result.getClueScrollEasy().getRank()); + Assert.assertEquals(911, result.getClueScrollMedium().getLevel()); + Assert.assertEquals(42, result.getClueScrollHard().getRank()); + Assert.assertEquals(777, result.getClueScrollElite().getLevel()); + Assert.assertEquals(254, result.getClueScrollMaster().getRank()); + Assert.assertEquals(-1, result.getLastManStanding().getLevel()); + } + +} diff --git a/http-service/src/test/java/net/runelite/http/service/xp/XpMapperTest.java b/http-service/src/test/java/net/runelite/http/service/xp/XpMapperTest.java index f337cb635d..2c24f9cc81 100644 --- a/http-service/src/test/java/net/runelite/http/service/xp/XpMapperTest.java +++ b/http-service/src/test/java/net/runelite/http/service/xp/XpMapperTest.java @@ -1,59 +1,59 @@ -/* - * Copyright (c) 2018, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.http.service.xp; - -import net.runelite.http.api.hiscore.HiscoreResult; -import net.runelite.http.api.hiscore.Skill; -import net.runelite.http.api.xp.XpData; -import net.runelite.http.service.xp.beans.XpEntity; -import static org.junit.Assert.assertEquals; -import org.junit.Test; - -public class XpMapperTest -{ - @Test - public void testXpEntityToXpData() - { - XpEntity xpEntity = new XpEntity(); - xpEntity.setAgility_rank(42); - xpEntity.setAgility_xp(9001); - - XpData xpData = XpMapper.INSTANCE.xpEntityToXpData(xpEntity); - assertEquals(42, xpData.getAgility_rank()); - assertEquals(9001, xpData.getAgility_xp()); - } - - @Test - public void testHiscoreResultToXpData() - { - HiscoreResult hiscoreResult = new HiscoreResult(); - hiscoreResult.setAgility(new Skill(42, 9, 9001)); - - XpData xpData = XpMapper.INSTANCE.hiscoreResultToXpData(hiscoreResult); - assertEquals(42, xpData.getAgility_rank()); - assertEquals(9001, xpData.getAgility_xp()); - } - -} +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.xp; + +import net.runelite.http.api.hiscore.HiscoreResult; +import net.runelite.http.api.hiscore.Skill; +import net.runelite.http.api.xp.XpData; +import net.runelite.http.service.xp.beans.XpEntity; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class XpMapperTest +{ + @Test + public void testXpEntityToXpData() + { + XpEntity xpEntity = new XpEntity(); + xpEntity.setAgility_rank(42); + xpEntity.setAgility_xp(9001); + + XpData xpData = XpMapper.INSTANCE.xpEntityToXpData(xpEntity); + assertEquals(42, xpData.getAgility_rank()); + assertEquals(9001, xpData.getAgility_xp()); + } + + @Test + public void testHiscoreResultToXpData() + { + HiscoreResult hiscoreResult = new HiscoreResult(); + hiscoreResult.setAgility(new Skill(42, 9, 9001)); + + XpData xpData = XpMapper.INSTANCE.hiscoreResultToXpData(hiscoreResult); + assertEquals(42, xpData.getAgility_rank()); + assertEquals(9001, xpData.getAgility_xp()); + } + +} diff --git a/injected-client/build.gradle b/injected-client/build.gradle new file mode 100644 index 0000000000..38c22b7dd0 --- /dev/null +++ b/injected-client/build.gradle @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 ThatGamerBlue + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +description = 'Injected Client' + +compileJava { + dependsOn ':injector-plugin:assemble' +} + +compileJava.outputs.upToDateWhen { false } + +compileJava.doLast() { + copy { + File f = file("build/classes/java/main") + f.deleteDir() + f.mkdirs() + from ("${injectedClassesPath}") + into ("build/classes/java/main") + } +} + +classes.doLast() { + File f = file("build/classes/java/main/Placeholder.class") + f.delete() +} diff --git a/injected-client/src/main/java/Placeholder.java b/injected-client/src/main/java/Placeholder.java new file mode 100644 index 0000000000..8486c0fc90 --- /dev/null +++ b/injected-client/src/main/java/Placeholder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 ThatGamerBlue + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @author ThatGamerBlue + * + * This file exists to force gradle to execute the compileJava task + * so we can hijack it and run the injector-plugin + */ +public class Placeholder +{ +} \ No newline at end of file diff --git a/injector-plugin/build.gradle b/injector-plugin/build.gradle new file mode 100644 index 0000000000..e6b59ad066 --- /dev/null +++ b/injector-plugin/build.gradle @@ -0,0 +1,61 @@ +group = 'net.runelite.rs' +description = 'Injector' + +def buildPath = buildDir.toString().replace('\\', '/') // this doesnt work in an ext block for some reason +def deobfuscatedJar = "${rootPath}/runescape-client/build/libs/rs-client-${project.version}.jar" +def vanillaJar = "${buildPath}/vanilla-${rsversion}.jar" + +configurations { + vanilla +} + +dependencies { + annotationProcessor group: 'org.eclipse.sisu', name: 'org.eclipse.sisu.inject', version: sisu + + compileOnly group: 'org.apache.maven.plugin-tools', name: 'maven-plugin-annotations', version: mavenPluginAnnotations + + implementation group: 'com.google.guava', name: 'guava', version: guava + implementation group: 'org.apache.maven', name: 'maven-plugin-api', version: mavenPluginApi + implementation group: 'org.ow2.asm', name: 'asm', version: asm + implementation group: 'org.ow2.asm', name: 'asm-util', version: asm + implementation project(':deobfuscator') + implementation project(':mixins') + implementation project(':runelite-api') + implementation project(':runescape-api') + + testImplementation group: 'junit', name: 'junit', version: junit + testImplementation group: 'org.mockito', name: 'mockito-core', version: mockito + testImplementation project(':deobfuscator') + testImplementation project(path: ':deobfuscator', configuration: 'testArchives') + + vanilla "net.runelite.rs:vanilla:${rsversion}" +} + +compileJava { + dependsOn ":rs-client:build" +} + +compileJava.outputs.upToDateWhen { false } + +compileJava.doLast() { + copy { + from configurations.vanilla + into "$buildDir" + } + def path = sourceSets.main.runtimeClasspath + def loader + try { + loader = new URLClassLoader(path.collect { f -> f.toURI().toURL() } as URL[]) + def inject = loader.loadClass('net.runelite.injector.Injector') + String[] jarPaths = [ + deobfuscatedJar.toString(), + vanillaJar.toString(), + injectedClassesPath.toString() + ] + inject.main(jarPaths) + } finally { + if (loader) { + loader.close() + } + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/Inject.java b/injector-plugin/src/main/java/net/runelite/injector/Inject.java new file mode 100644 index 0000000000..942d349729 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/Inject.java @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.HashMap; +import java.util.Map; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Field; +import net.runelite.asm.Interfaces; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotations; +import net.runelite.asm.attributes.annotation.Annotation; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.DLoad; +import net.runelite.asm.attributes.code.instructions.FLoad; +import net.runelite.asm.attributes.code.instructions.ILoad; +import net.runelite.asm.attributes.code.instructions.LLoad; +import net.runelite.asm.pool.Class; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import net.runelite.deob.deobfuscators.arithmetic.DMath; +import static net.runelite.injector.InjectUtil.getFieldType; +import net.runelite.injector.raw.ClearColorBuffer; +import net.runelite.injector.raw.DrawAfterWidgets; +import net.runelite.injector.raw.DrawMenu; +import net.runelite.injector.raw.HidePlayerAttacks; +import net.runelite.injector.raw.Occluder; +import net.runelite.injector.raw.RasterizerHook; +import net.runelite.injector.raw.RenderDraw; +import net.runelite.injector.raw.ScriptVM; +import net.runelite.mapping.Import; +import net.runelite.rs.api.RSClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Inject +{ + public static final java.lang.Class CLIENT_CLASS = RSClient.class; + public static final String API_PACKAGE_BASE = "net.runelite.rs.api.RS"; + public static final String RL_API_PACKAGE_BASE = "net.runelite.api."; + private static final Logger logger = LoggerFactory.getLogger(Inject.class); + private final InjectHookMethod hookMethod = new InjectHookMethod(this); + + private final InjectGetter getters = new InjectGetter(this); + private final InjectSetter setters = new InjectSetter(this); + private final InjectInvoker invokes = new InjectInvoker(this); + private final InjectConstruct construct = new InjectConstruct(this); + + private final MixinInjector mixinInjector = new MixinInjector(this); + + // deobfuscated contains exports etc to apply to vanilla + private final ClassGroup deobfuscated, vanilla; + + public Inject(ClassGroup deobfuscated, ClassGroup vanilla) + { + this.deobfuscated = deobfuscated; + this.vanilla = vanilla; + } + + /** + * Convert a java.lang.Class to a Type + * + * @param c + * @return + */ + public static Type classToType(java.lang.Class c) + { + int dimms = 0; + while (c.isArray()) + { + c = c.getComponentType(); + ++dimms; + } + + if (c.isPrimitive()) + { + String s; + + switch (c.getName()) + { + case "int": + s = "I"; + break; + case "long": + s = "J"; + break; + case "boolean": + s = "Z"; + break; + case "char": + s = "C"; + break; + case "short": + s = "S"; + break; + case "float": + s = "F"; + break; + case "double": + s = "D"; + break; + case "byte": + s = "B"; + break; + case "void": + s = "V"; + break; + default: + throw new RuntimeException("unknown primitive type " + c.getName()); + } + + return Type.getType(s, dimms); + } + + return Type.getType("L" + c.getName().replace('.', '/') + ";", dimms); + } + + public Signature getMethodSignature(Method m) + { + Signature signature = m.getDescriptor(); + + Annotation obfSignature = m.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE); + if (obfSignature != null) + { + //Annotation exists. Signature was updated by us during deobfuscation + signature = DeobAnnotations.getObfuscatedSignature(m); + } + + return signature; + } + + /** + * Build a Signature from a java method + * + * @param method + * @return + */ + public Signature javaMethodToSignature(java.lang.reflect.Method method) + { + Signature.Builder builder = new Signature.Builder() + .setReturnType(classToType(method.getReturnType())); + for (java.lang.Class clazz : method.getParameterTypes()) + { + builder.addArgument(classToType(clazz)); + } + return builder.build(); + } + + public void run() throws InjectionException + { + Map implemented = new HashMap<>(); + + // inject interfaces first, so the validateTypeIsConvertibleTo + // check below works + for (ClassFile cf : deobfuscated.getClasses()) + { + Annotations an = cf.getAnnotations(); + + if (an == null || an.size() == 0) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(an); + if (obfuscatedName == null) + { + obfuscatedName = cf.getName(); + } + + ClassFile other = vanilla.findClass(obfuscatedName); + assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName; + + java.lang.Class implementingClass = injectInterface(cf, other); + // it can not implement an interface but still have exported static fields, which are + // moved to client + + implemented.put(cf, implementingClass); + } + + // Has to be done before mixins + // well, can be done after really + // but why do that when you can do it before + new RasterizerHook(this).inject(); + + // requires interfaces to be injected + mixinInjector.inject(); + construct.inject(implemented); + + for (ClassFile cf : deobfuscated.getClasses()) + { + java.lang.Class implementingClass = implemented.get(cf); + Annotations an = cf.getAnnotations(); + + if (an == null || an.size() == 0) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(an); + if (obfuscatedName == null) + { + obfuscatedName = cf.getName(); + } + + ClassFile other = vanilla.findClass(obfuscatedName); + assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName; + + for (Field f : cf.getFields()) + { + an = f.getAnnotations(); + + if (an == null || an.find(DeobAnnotations.EXPORT) == null) + { + continue; // not an exported field + } + + Annotation exportAnnotation = an.find(DeobAnnotations.EXPORT); + String exportedName = exportAnnotation.getElement().getString(); + + obfuscatedName = DeobAnnotations.getObfuscatedName(an); + + Annotation getterAnnotation = an.find(DeobAnnotations.OBFUSCATED_GETTER); + Number getter = null; + if (getterAnnotation != null) + { + getter = (Number) getterAnnotation.getElement().getValue(); + } + // the ob jar is the same as the vanilla so this field must exist in this class. + + Type obType = getFieldType(f); + Field otherf = other.findField(obfuscatedName, obType); + assert otherf != null; + + assert f.isStatic() == otherf.isStatic(); + + ClassFile targetClass = f.isStatic() ? vanilla.findClass("client") : other; // target class for getter + java.lang.Class targetApiClass = f.isStatic() ? CLIENT_CLASS : implementingClass; // target api class for getter + if (targetApiClass == null) + { + assert !f.isStatic(); + + // non static field exported on non exported interface + // logger.debug("Non static exported field {} on non exported interface", exportedName); + continue; + } + + java.lang.reflect.Method apiMethod = findImportMethodOnApi(targetApiClass, exportedName, true); + if (apiMethod != null) + { + Number setter = null; + if (getter != null) + { + setter = DMath.modInverse(getter); // inverse getter to get the setter + } + + setters.injectSetter(targetClass, targetApiClass, otherf, exportedName, setter); + } + + apiMethod = findImportMethodOnApi(targetApiClass, exportedName, false); + if (apiMethod == null) + { + //logger.debug("Unable to find import method on api class {} with imported name {}, not injecting getter", targetApiClass, exportedName); + continue; + } + + // check that otherf is converable to apiMethod's + // return type + Type fieldType = otherf.getType(); + Type returnType = classToType(apiMethod.getReturnType()); + if (!validateTypeIsConvertibleTo(fieldType, returnType)) + { + throw new InjectionException("Type " + fieldType + " is not convertable to " + returnType + " for getter " + apiMethod); + } + + getters.injectGetter(targetClass, apiMethod, otherf, getter); + } + + for (Method m : cf.getMethods()) + { + hookMethod.process(m); + invokes.process(m, other, implementingClass); + } + } + + logger.info("Injected {} getters, {} setters, {} invokers", + getters.getInjectedGetters(), + setters.getInjectedSetters(), invokes.getInjectedInvokers()); + + new DrawAfterWidgets(this).inject(); + new ScriptVM(this).inject(); + new ClearColorBuffer(this).inject(); + new RenderDraw(this).inject(); + new DrawMenu(this).inject(); + new Occluder(this).inject(); + new HidePlayerAttacks(this).inject(); + } + + private java.lang.Class injectInterface(ClassFile cf, ClassFile other) + { + Annotations an = cf.getAnnotations(); + if (an == null) + { + return null; + } + + Annotation a = an.find(DeobAnnotations.IMPLEMENTS); + if (a == null) + { + return null; + } + + String ifaceName = API_PACKAGE_BASE + a.getElement().getString(); + java.lang.Class apiClass; + + try + { + apiClass = java.lang.Class.forName(ifaceName); + } + catch (ClassNotFoundException ex) + { + logger.trace("Class {} implements nonexistent interface {}, skipping interface injection", + cf.getName(), + ifaceName); + return null; + } + + String ifaceNameInternal = ifaceName.replace('.', '/'); // to internal name + Class clazz = new Class(ifaceNameInternal); + + Interfaces interfaces = other.getInterfaces(); + interfaces.addInterface(clazz); + + return apiClass; + } + + public java.lang.reflect.Method findImportMethodOnApi(java.lang.Class clazz, String name, Boolean setter) + { + for (java.lang.reflect.Method method : clazz.getDeclaredMethods()) + { + if (method.isSynthetic()) + { + /* + * If you override an interface method in another interface + * with a return type that is a child of the overriden methods + * return type, both methods end up in the interface, and both + * are *annotated*. But the base one is synthetic. + */ + continue; + } + + Import i = method.getAnnotation(Import.class); + + if (i == null || !name.equals(i.value()) || (setter != null && (method.getParameterCount() > 0) != setter)) + { + continue; + } + + return method; + } + + return null; + } + + /** + * create a load instruction for a variable of type from a given index + * + * @param instructions + * @param type + * @param index + * @return + */ + public Instruction createLoadForTypeIndex(Instructions instructions, Type type, int index) + { + if (type.getDimensions() > 0 || !type.isPrimitive()) + { + return new ALoad(instructions, index); + } + + switch (type.toString()) + { + case "B": + case "C": + case "I": + case "S": + case "Z": + return new ILoad(instructions, index); + case "D": + return new DLoad(instructions, index); + case "F": + return new FLoad(instructions, index); + case "J": + return new LLoad(instructions, index); + default: + throw new RuntimeException("Unknown type"); + } + } + + ClassFile toDeobClass(ClassFile obClass) + { + for (ClassFile cf : deobfuscated.getClasses()) + { + String obfuscatedName = DeobAnnotations.getObfuscatedName(cf.getAnnotations()); + + if (obClass.getName().equalsIgnoreCase(obfuscatedName)) + { + return cf; + } + } + + return null; + } + + Type deobfuscatedTypeToApiType(Type type) throws InjectionException + { + if (type.isPrimitive()) + { + return type; + } + + ClassFile cf = deobfuscated.findClass(type.getInternalName()); + if (cf == null) + { + return type; // not my type + } + + java.lang.Class rsApiType; + try + { + rsApiType = java.lang.Class.forName(API_PACKAGE_BASE + cf.getName().replace("/", ".")); + } + catch (ClassNotFoundException ex) + { + throw new InjectionException("Deobfuscated type " + type.getInternalName() + " has no API type", ex); + } + + java.lang.Class rlApiType = null; + + for (java.lang.Class inter : rsApiType.getInterfaces()) + { + if (inter.getName().startsWith(RL_API_PACKAGE_BASE)) + { + rlApiType = inter; + } + } + + // if (rlApiType == null) + // { + // throw new InjectionException("RS API type " + rsApiType + " does not extend RL API interface"); + // } + + final java.lang.Class finalType = rlApiType == null ? rsApiType : rlApiType; + + return Type.getType("L" + finalType.getName().replace('.', '/') + ";", type.getDimensions()); + } + + Type apiTypeToDeobfuscatedType(Type type) + { + if (type.isPrimitive()) + { + return type; + } + + String internalName = type.getInternalName().replace('/', '.'); + if (!internalName.startsWith(API_PACKAGE_BASE)) + { + return type; // not an rs api type + } + + return Type.getType("L" + type.getInternalName().substring(API_PACKAGE_BASE.length()) + ";", type.getDimensions()); + } + + ClassFile findVanillaForInterface(java.lang.Class clazz) + { + String className = clazz.getName().replace('.', '/'); + for (ClassFile cf : getVanilla().getClasses()) + { + for (net.runelite.asm.pool.Class cl : cf.getInterfaces().getInterfaces()) + { + if (cl.getName().equals(className)) + { + return cf; + } + } + } + return null; + } + + private boolean validateTypeIsConvertibleTo(Type from, Type to) throws InjectionException + { + if (from.getDimensions() != to.getDimensions()) + { + throw new InjectionException("Array dimension mismatch"); + } + + if (from.isPrimitive()) + { + return true; + } + + ClassFile vanillaClass = vanilla.findClass(from.getInternalName()); + if (vanillaClass == null) + { + return true; + } + + boolean okay = false; + for (Class inter : vanillaClass.getInterfaces().getInterfaces()) + { + java.lang.Class c; + + try + { + c = java.lang.Class.forName(inter.getName().replace('/', '.')); + } + catch (ClassNotFoundException ex) + { + continue; + } + + okay |= check(c, to); + } + + return okay; + } + + private boolean check(java.lang.Class c, Type type) + { + String s = type.getInternalName() + .replace('/', '.'); + + if (c.getName().equals(s)) + { + return true; + } + + for (java.lang.Class c2 : c.getInterfaces()) + { + if (check(c2, type)) + { + return true; + } + } + return false; + } + + public final ClassGroup getDeobfuscated() + { + return deobfuscated; + } + + public final ClassGroup getVanilla() + { + return vanilla; + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectConstruct.java b/injector-plugin/src/main/java/net/runelite/injector/InjectConstruct.java new file mode 100644 index 0000000000..8a87451da6 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectConstruct.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.Dup; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import net.runelite.asm.attributes.code.instructions.New; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import net.runelite.mapping.Construct; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InjectConstruct +{ + private static final Logger logger = LoggerFactory.getLogger(InjectConstruct.class); + + private final Inject inject; + + InjectConstruct(Inject inject) + { + this.inject = inject; + } + + public void inject(Map implemented) throws InjectionException + { + for (Entry entry : implemented.entrySet()) + { + Class clazz = entry.getValue(); + ClassFile cf = entry.getKey(); + + if (clazz == null) + { + continue; + } + + for (java.lang.reflect.Method method : clazz.getDeclaredMethods()) + { + if (method.isSynthetic()) + { + continue; + } + + Construct construct = method.getAnnotation(Construct.class); + if (construct == null) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(cf.getAnnotations()); + if (obfuscatedName == null) + { + obfuscatedName = cf.getName(); + } + + ClassGroup vanilla = inject.getVanilla(); + ClassFile other = vanilla.findClass(obfuscatedName); + assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName; + + injectConstruct(other, method); + } + } + } + + void injectConstruct(ClassFile targetClass, java.lang.reflect.Method apiMethod) throws InjectionException + { + logger.info("Injecting construct for {}", apiMethod); + + assert targetClass.findMethod(apiMethod.getName()) == null; + + Class typeToConstruct = apiMethod.getReturnType(); + ClassFile vanillaClass = inject.findVanillaForInterface(typeToConstruct); + if (vanillaClass == null) + { + throw new InjectionException("Unable to find vanilla class which implements interface " + typeToConstruct); + } + + Signature sig = inject.javaMethodToSignature(apiMethod); + + Signature constructorSig = new Signature.Builder() + .addArguments(Stream.of(apiMethod.getParameterTypes()) + .map(arg -> + { + ClassFile vanilla = inject.findVanillaForInterface(arg); + if (vanilla != null) + { + return new Type("L" + vanilla.getName() + ";"); + } + return Inject.classToType(arg); + }) + .collect(Collectors.toList())) + .setReturnType(Type.VOID) + .build(); + Method vanillaConstructor = vanillaClass.findMethod("", constructorSig); + if (vanillaConstructor == null) + { + throw new InjectionException("Unable to find constructor for " + vanillaClass.getName() + "." + constructorSig); + } + + Method setterMethod = new Method(targetClass, apiMethod.getName(), sig); + setterMethod.setAccessFlags(ACC_PUBLIC); + targetClass.addMethod(setterMethod); + + Code code = new Code(setterMethod); + setterMethod.setCode(code); + + Instructions instructions = code.getInstructions(); + List ins = instructions.getInstructions(); + + ins.add(new New(instructions, vanillaClass.getPoolClass())); + ins.add(new Dup(instructions)); + int idx = 1; + int parameter = 0; + for (Type type : vanillaConstructor.getDescriptor().getArguments()) + { + Instruction load = inject.createLoadForTypeIndex(instructions, type, idx); + idx += type.getSize(); + ins.add(load); + + Type paramType = sig.getTypeOfArg(parameter); + if (!type.equals(paramType)) + { + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(type); + ins.add(checkCast); + } + + ++parameter; + } + ins.add(new InvokeSpecial(instructions, vanillaConstructor.getPoolMethod())); + ins.add(new Return(instructions)); + + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectGetter.java b/injector-plugin/src/main/java/net/runelite/injector/InjectGetter.java new file mode 100644 index 0000000000..cd94ee80d8 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectGetter.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.List; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.GetField; +import net.runelite.asm.attributes.code.instructions.GetStatic; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LMul; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.signature.Signature; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InjectGetter +{ + private static final Logger logger = LoggerFactory.getLogger(InjectGetter.class); + + private final Inject inject; + + private int injectedGetters; + + InjectGetter(Inject inject) + { + this.inject = inject; + } + + void injectGetter(ClassFile clazz, java.lang.reflect.Method method, Field field, Number getter) + { + // clazz = class file we're injecting the method into. + // method = api method (java reflect) that we're overriding + // field = field we're getting. might not be in this class if static. + // getter = encryption getter + + assert clazz.findMethod(method.getName()) == null; + assert field.isStatic() || field.getClassFile() == clazz; + + Signature sig = new Signature.Builder() + .setReturnType(Inject.classToType(method.getReturnType())) + .build(); + Method getterMethod = new Method(clazz, method.getName(), sig); + getterMethod.setAccessFlags(ACC_PUBLIC); + + // create code + Code code = new Code(getterMethod); + getterMethod.setCode(code); + + Instructions instructions = code.getInstructions(); + List ins = instructions.getInstructions(); + + if (field.isStatic()) + { + code.setMaxStack(1); + + ins.add(new GetStatic(instructions, field.getPoolField())); + } + else + { + code.setMaxStack(2); + + ins.add(new ALoad(instructions, 0)); + ins.add(new GetField(instructions, field.getPoolField())); + } + + if (getter != null) + { + code.setMaxStack(2); + + assert getter instanceof Integer || getter instanceof Long; + + if (getter instanceof Integer) + { + ins.add(new LDC(instructions, (int) getter)); + ins.add(new IMul(instructions)); + } + else + { + ins.add(new LDC(instructions, (long) getter)); + ins.add(new LMul(instructions)); + } + } + + InstructionType returnType; + if (field.getType().isPrimitive() && field.getType().getDimensions() == 0) + { + switch (field.getType().toString()) + { + case "B": + case "C": + case "I": + case "S": + case "Z": + returnType = InstructionType.IRETURN; + break; + case "D": + returnType = InstructionType.DRETURN; + break; + case "F": + returnType = InstructionType.FRETURN; + break; + case "J": + returnType = InstructionType.LRETURN; + break; + default: + throw new RuntimeException("Unknown type"); + } + } + else + { + returnType = InstructionType.ARETURN; + } + + ins.add(new Return(instructions, returnType)); + + clazz.addMethod(getterMethod); + ++injectedGetters; + } + + int getInjectedGetters() + { + return injectedGetters; + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectHook.java b/injector-plugin/src/main/java/net/runelite/injector/InjectHook.java new file mode 100644 index 0000000000..19ae3a3df6 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectHook.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import com.google.common.collect.Lists; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.DupInstruction; +import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction; +import net.runelite.asm.attributes.code.instructions.ArrayStore; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.Dup; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LMul; +import net.runelite.asm.attributes.code.instructions.PutField; +import net.runelite.asm.attributes.code.instructions.Swap; +import net.runelite.asm.execution.Execution; +import net.runelite.asm.execution.InstructionContext; +import net.runelite.asm.execution.StackContext; +import net.runelite.asm.signature.Signature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InjectHook +{ + private static final Logger logger = LoggerFactory.getLogger(InjectHook.class); + private static final String HOOK_METHOD_SIGNATURE = "(I)V"; + private static final String CLINIT = ""; + private final Inject inject; + private final Map hooked = new HashMap<>(); + private int injectedHooks; + + InjectHook(Inject inject) + { + this.inject = inject; + } + + void hook(Field field, HookInfo hookInfo) + { + hooked.put(field, hookInfo); + } + + void run() + { + Execution e = new Execution(inject.getVanilla()); + e.populateInitialMethods(); + + Set done = new HashSet<>(); + Set doneIh = new HashSet<>(); + + e.addExecutionVisitor((InstructionContext ic) -> + { + Instruction i = ic.getInstruction(); + Instructions ins = i.getInstructions(); + Code code = ins.getCode(); + Method method = code.getMethod(); + + if (method.getName().equals(CLINIT)) + { + return; + } + + if (!(i instanceof SetFieldInstruction)) + { + return; + } + + if (!done.add(i)) + { + return; + } + + SetFieldInstruction sfi = (SetFieldInstruction) i; + Field fieldBeingSet = sfi.getMyField(); + + if (fieldBeingSet == null) + { + return; + } + + HookInfo hookInfo = hooked.get(fieldBeingSet); + if (hookInfo == null) + { + return; + } + + String hookName = hookInfo.fieldName; + assert hookName != null; + + logger.trace("Found injection location for hook {} at instruction {}", hookName, sfi); + ++injectedHooks; + + StackContext value = ic.getPops().get(0); + + StackContext objectStackContext = null; + if (sfi instanceof PutField) + { + objectStackContext = ic.getPops().get(1); + } + + int idx = ins.getInstructions().indexOf(sfi); + assert idx != -1; + + try + { + if (hookInfo.before) + { + injectCallbackBefore(ins, idx, hookInfo, null, objectStackContext, value); + } + else + { + // idx + 1 to insert after the set + injectCallback(ins, idx + 1, hookInfo, null, objectStackContext); + } + } + catch (InjectionException ex) + { + throw new RuntimeException(ex); + } + }); + + // these look like: + // getfield + // iload_0 + // iconst_0 + // iastore + e.addExecutionVisitor((InstructionContext ic) -> + { + Instruction i = ic.getInstruction(); + Instructions ins = i.getInstructions(); + Code code = ins.getCode(); + Method method = code.getMethod(); + + if (method.getName().equals(CLINIT)) + { + return; + } + + if (!(i instanceof ArrayStore)) + { + return; + } + + if (!doneIh.add(i)) + { + return; + } + + ArrayStore as = (ArrayStore) i; + + Field fieldBeingSet = as.getMyField(ic); + if (fieldBeingSet == null) + { + return; + } + + HookInfo hookInfo = hooked.get(fieldBeingSet); + if (hookInfo == null) + { + return; + } + + String hookName = hookInfo.fieldName; + + StackContext value = ic.getPops().get(0); + StackContext index = ic.getPops().get(1); + + StackContext arrayReference = ic.getPops().get(2); + InstructionContext arrayReferencePushed = arrayReference.getPushed(); + + StackContext objectStackContext = null; + if (arrayReferencePushed.getInstruction().getType() == InstructionType.GETFIELD) + { + objectStackContext = arrayReferencePushed.getPops().get(0); + } + + // inject hook after 'i' + logger.info("Found array injection location for hook {} at instruction {}", hookName, i); + ++injectedHooks; + + int idx = ins.getInstructions().indexOf(i); + assert idx != -1; + + try + { + if (hookInfo.before) + { + injectCallbackBefore(ins, idx, hookInfo, index, objectStackContext, value); + } + else + { + injectCallback(ins, idx + 1, hookInfo, index, objectStackContext); + } + } + catch (InjectionException ex) + { + throw new RuntimeException(ex); + } + }); + + e.run(); + } + + private void injectCallbackBefore(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext object, StackContext value) throws InjectionException + { + Signature signature = hookInfo.method.getDescriptor(); + Type methodArgumentType = signature.getTypeOfArg(0); + + if (!hookInfo.method.isStatic()) + { + if (object == null) + { + throw new InjectionException("null object"); + } + + ins.getInstructions().add(idx++, new Dup(ins)); // dup value + idx = recursivelyPush(ins, idx, object); + ins.getInstructions().add(idx++, new Swap(ins)); + if (hookInfo.getter != null) + { + assert hookInfo.getter instanceof Integer || hookInfo.getter instanceof Long; + + if (hookInfo.getter instanceof Integer) + { + ins.getInstructions().add(idx++, new LDC(ins, (int) hookInfo.getter)); + ins.getInstructions().add(idx++, new IMul(ins)); + } + else + { + ins.getInstructions().add(idx++, new LDC(ins, (long) hookInfo.getter)); + ins.getInstructions().add(idx++, new LMul(ins)); + } + } + if (!value.type.equals(methodArgumentType)) + { + CheckCast checkCast = new CheckCast(ins); + checkCast.setType(methodArgumentType); + ins.getInstructions().add(idx++, checkCast); + } + if (index != null) + { + idx = recursivelyPush(ins, idx, index); + } + + InvokeVirtual invoke = new InvokeVirtual(ins, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(hookInfo.clazz), + hookInfo.method.getName(), + signature + ) + ); + ins.getInstructions().add(idx++, invoke); + } + else + { + ins.getInstructions().add(idx++, new Dup(ins)); // dup value + if (!value.type.equals(methodArgumentType)) + { + CheckCast checkCast = new CheckCast(ins); + checkCast.setType(methodArgumentType); + ins.getInstructions().add(idx++, checkCast); + } + if (index != null) + { + idx = recursivelyPush(ins, idx, index); + } + + InvokeStatic invoke = new InvokeStatic(ins, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(hookInfo.clazz), + hookInfo.method.getName(), + signature + ) + ); + ins.getInstructions().add(idx++, invoke); + } + } + + private int recursivelyPush(Instructions ins, int idx, StackContext sctx) + { + InstructionContext ctx = sctx.getPushed(); + if (ctx.getInstruction() instanceof DupInstruction) + { + DupInstruction dupInstruction = (DupInstruction) ctx.getInstruction(); + sctx = dupInstruction.getOriginal(sctx); + ctx = sctx.getPushed(); + } + + for (StackContext s : Lists.reverse(ctx.getPops())) + { + idx = recursivelyPush(ins, idx, s); + } + + ins.getInstructions().add(idx++, ctx.getInstruction().clone()); + return idx; + } + + private void injectCallback(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext objectPusher) throws InjectionException + { + if (!hookInfo.method.isStatic()) + { + if (objectPusher == null) + { + throw new InjectionException("Null object pusher"); + } + + idx = recursivelyPush(ins, idx, objectPusher); + if (index != null) + { + idx = recursivelyPush(ins, idx, index); + } + else + { + ins.getInstructions().add(idx++, new LDC(ins, -1)); + } + + InvokeVirtual invoke = new InvokeVirtual(ins, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(hookInfo.clazz), + hookInfo.method.getName(), + new Signature(HOOK_METHOD_SIGNATURE) + ) + ); + ins.getInstructions().add(idx++, invoke); + + } + else + { + if (index != null) + { + idx = recursivelyPush(ins, idx, index); + } + else + { + ins.getInstructions().add(idx++, new LDC(ins, -1)); + } + + InvokeStatic invoke = new InvokeStatic(ins, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(hookInfo.clazz), + hookInfo.method.getName(), + new Signature(HOOK_METHOD_SIGNATURE) + ) + ); + ins.getInstructions().add(idx++, invoke); + } + } + + int getInjectedHooks() + { + return injectedHooks; + } + + static class HookInfo + { + String fieldName; + String clazz; + Method method; + boolean before; + Number getter; + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectHookMethod.java b/injector-plugin/src/main/java/net/runelite/injector/InjectHookMethod.java new file mode 100644 index 0000000000..3c304807be --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectHookMethod.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotations; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.annotation.Annotation; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction; +import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InjectHookMethod +{ + public static final String HOOKS = "net/runelite/client/callback/Hooks"; + private static final Logger logger = LoggerFactory.getLogger(InjectHookMethod.class); + private final Inject inject; + + InjectHookMethod(Inject inject) + { + this.inject = inject; + } + + void process(Method method) throws InjectionException + { + Annotations an = method.getAnnotations(); + if (an == null) + { + return; + } + + Annotation a = an.find(DeobAnnotations.HOOK); + if (a == null) + { + return; + } + + String hookName = a.getElement().getString(); + boolean end = a.getElements().size() == 2 && a.getElements().get(1).getValue().equals(true); + + inject(null, method, hookName, end, true); + } + + public void inject(Method hookMethod, Method method, String name, boolean end, boolean useHooks) throws InjectionException + { + Annotations an = method.getAnnotations(); + + // Method is hooked + // Find equivalent method in vanilla, and insert callback at the beginning + ClassFile cf = method.getClassFile(); + String obfuscatedMethodName = DeobAnnotations.getObfuscatedName(an), + obfuscatedClassName = DeobAnnotations.getObfuscatedName(cf.getAnnotations()); + + // might be a constructor + if (obfuscatedMethodName == null) + { + obfuscatedMethodName = method.getName(); + } + + assert obfuscatedClassName != null : "hook on method in class with no obfuscated name"; + assert obfuscatedMethodName != null : "hook on method with no obfuscated name"; + + Signature obfuscatedSignature = inject.getMethodSignature(method); + + ClassGroup vanilla = inject.getVanilla(); + ClassFile vanillaClass = vanilla.findClass(obfuscatedClassName); + Method vanillaMethod = vanillaClass.findMethod(obfuscatedMethodName, obfuscatedSignature); + assert method.isStatic() == vanillaMethod.isStatic(); + + // Insert instructions at beginning of method + injectHookMethod(hookMethod, name, end, method, vanillaMethod, useHooks); + } + + private void injectHookMethod(Method hookMethod, String hookName, boolean end, Method deobMethod, Method vanillaMethod, boolean useHooks) throws InjectionException + { + Code code = vanillaMethod.getCode(); + if (code == null) + { + logger.warn(vanillaMethod + " code is null"); + } + Instructions instructions = code.getInstructions(); + + Signature.Builder builder = new Signature.Builder() + .setReturnType(Type.VOID); // Hooks always return void + + for (Type type : deobMethod.getDescriptor().getArguments()) + { + builder.addArgument(inject.deobfuscatedTypeToApiType(type)); + } + + assert deobMethod.isStatic() == vanillaMethod.isStatic(); + + boolean modifiedSignature = false; + if (!deobMethod.isStatic() && useHooks) + { + // Add variable to signature + builder.addArgument(0, inject.deobfuscatedTypeToApiType(new Type(deobMethod.getClassFile().getName()))); + modifiedSignature = true; + } + + Signature signature = builder.build(); + + List insertIndexes = findHookLocations(hookName, end, vanillaMethod); + insertIndexes.sort((a, b) -> Integer.compare(b, a)); + + for (int insertPos : insertIndexes) + { + if (!deobMethod.isStatic()) + { + instructions.addInstruction(insertPos++, new ALoad(instructions, 0)); + } + + int signatureStart = modifiedSignature ? 1 : 0; + int index = deobMethod.isStatic() ? 0 : 1; // current variable index + + for (int i = signatureStart; i < signature.size(); ++i) + { + Type type = signature.getTypeOfArg(i); + + Instruction load = inject.createLoadForTypeIndex(instructions, type, index); + instructions.addInstruction(insertPos++, load); + + index += type.getSize(); + } + + InvokeInstruction invoke; + + // use old Hooks callback + if (useHooks) + { + // Invoke callback + invoke = new InvokeStatic(instructions, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(HOOKS), + hookName, + signature + ) + ); + } + else + { + // Invoke methodhook + assert hookMethod != null; + + if (vanillaMethod.isStatic()) + { + invoke = new InvokeStatic(instructions, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class("client"), // Static methods are in client + hookMethod.getName(), + signature + ) + ); + } + else + { + // otherwise invoke member function + //instructions.addInstruction(insertPos++, new ALoad(instructions, 0)); + invoke = new InvokeVirtual(instructions, + new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(vanillaMethod.getClassFile().getName()), + hookMethod.getName(), + hookMethod.getDescriptor() + ) + ); + } + } + + instructions.addInstruction(insertPos++, (Instruction) invoke); + } + + logger.info("Injected method hook {} in {} with {} args: {}", + hookName, vanillaMethod, signature.size(), + signature.getArguments()); + } + + private List findHookLocations(String hookName, boolean end, Method vanillaMethod) throws InjectionException + { + Instructions instructions = vanillaMethod.getCode().getInstructions(); + + if (end) + { + // find return + List returns = instructions.getInstructions().stream() + .filter(i -> i instanceof ReturnInstruction) + .collect(Collectors.toList()); + List indexes = new ArrayList<>(); + + for (Instruction ret : returns) + { + int idx = instructions.getInstructions().indexOf(ret); + assert idx != -1; + indexes.add(idx); + } + + return indexes; + } + + if (!vanillaMethod.getName().equals("")) + { + return Arrays.asList(0); + } + + // Find index after invokespecial + for (int i = 0; i < instructions.getInstructions().size(); ++i) + { + Instruction in = instructions.getInstructions().get(i); + + if (in.getType() == InstructionType.INVOKESPECIAL) + { + return Arrays.asList(i + 1); // one after + } + } + + throw new IllegalStateException("constructor with no invokespecial"); + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectInvoker.java b/injector-plugin/src/main/java/net/runelite/injector/InjectInvoker.java new file mode 100644 index 0000000000..3367b26c7a --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectInvoker.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.List; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotations; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.BiPush; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.DLoad; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LLoad; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.attributes.code.instructions.SiPush; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import static net.runelite.deob.DeobAnnotations.EXPORT; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InjectInvoker +{ + private static final Logger logger = LoggerFactory.getLogger(InjectInvoker.class); + + private final Inject inject; + + private int injectedInvokers; + + InjectInvoker(Inject inject) + { + this.inject = inject; + } + + /** + * Inject an invoker for a method + * + * @param m Method in the deobfuscated client to inject an invoker for + * @param other Class in the vanilla client of the same class m is a + * member of + * @param implementingClass Java class for the API interface the class + * will implement + */ + void process(Method m, ClassFile other, java.lang.Class implementingClass) + { + Annotations an = m.getAnnotations(); + + if (an == null || an.find(EXPORT) == null) + { + return; // not an exported method + } + + String exportedName = DeobAnnotations.getExportedName(an); + String obfuscatedName = DeobAnnotations.getObfuscatedName(an); + + if (obfuscatedName == null) + { + obfuscatedName = m.getName(); + } + + String garbage = DeobAnnotations.getDecoder(m); + Method otherm = other.findMethod(obfuscatedName, inject.getMethodSignature(m)); + + assert otherm != null; + assert m.isStatic() == otherm.isStatic(); + + ClassGroup vanilla = inject.getVanilla(); + + ClassFile targetClass = m.isStatic() ? vanilla.findClass("client") : other; + + // Place into implementing class, unless the method is static + java.lang.Class targetClassJava = m.isStatic() ? Inject.CLIENT_CLASS : implementingClass; + + if (targetClassJava == null) + { + assert !m.isStatic(); + + // non static exported method on non exported interface, weird. + // logger.debug("Non static exported method {} on non exported interface", exportedName); + return; + } + + java.lang.reflect.Method apiMethod = inject.findImportMethodOnApi(targetClassJava, exportedName, null); // api method to invoke 'otherm' + if (apiMethod == null) + { + // logger.debug("Unable to find api method on {} with imported name {}, not injecting invoker", targetClassJava, exportedName); + return; + } + + injectInvoker(targetClass, apiMethod, m, otherm, garbage); + ++injectedInvokers; + } + + private void injectInvoker(ClassFile clazz, java.lang.reflect.Method method, Method deobfuscatedMethod, Method invokeMethod, String garbage) + { + // clazz = clazz to add invoker to + // method = api method to override + // deobfuscatedMethod = deobfuscated method, used to get the deobfuscated signature + // invokeMethod = method to invoke, obfuscated + + if (clazz.findMethod(method.getName(), deobfuscatedMethod.getDescriptor()) != null) + { + logger.warn("Not injecting method {} because it already exists!", method); + return; // this can happen from exporting a field and method with the same name + } + + assert invokeMethod.isStatic() == deobfuscatedMethod.isStatic(); + assert invokeMethod.isStatic() || invokeMethod.getClassFile() == clazz; + + Type lastGarbageArgumentType = null; + + if (deobfuscatedMethod.getDescriptor().getArguments().size() != invokeMethod.getDescriptor().getArguments().size()) + { + // allow for obfuscated method to have a single bogus signature at the end + assert deobfuscatedMethod.getDescriptor().size() + 1 == invokeMethod.getDescriptor().size(); + + List arguments = invokeMethod.getDescriptor().getArguments(); + lastGarbageArgumentType = arguments.get(arguments.size() - 1); + } + + // Injected method signature is always the same as the API + Signature apiSignature = inject.javaMethodToSignature(method); + Method invokerMethodSignature = new Method(clazz, method.getName(), apiSignature); + invokerMethodSignature.setAccessFlags(ACC_PUBLIC); + + // create code attribute + Code code = new Code(invokerMethodSignature); + invokerMethodSignature.setCode(code); + + Instructions instructions = code.getInstructions(); + List ins = instructions.getInstructions(); + + code.setMaxStack(1 + invokeMethod.getDescriptor().size()); // this + arguments + + // load function arguments onto the stack. + int index = 0; + if (!invokeMethod.isStatic()) + { + ins.add(new ALoad(instructions, index++)); // this + } + else + { + ++index; // this method is always non static + } + for (int i = 0; i < deobfuscatedMethod.getDescriptor().size(); ++i) + { + Type type = deobfuscatedMethod.getDescriptor().getTypeOfArg(i); + + Instruction loadInstruction = inject.createLoadForTypeIndex(instructions, type, index); + ins.add(loadInstruction); + + Signature invokeDesc = invokeMethod.getDescriptor(); + Type obType = invokeDesc.getTypeOfArg(i); + if (!type.equals(obType)) + { + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(obType); + ins.add(checkCast); + } + + if (loadInstruction instanceof DLoad || loadInstruction instanceof LLoad) + { + index += 2; + } + else + { + index += 1; + } + } + + if (lastGarbageArgumentType != null) + { + // function requires garbage value + + // if garbage is null here it might just be an unused parameter, not part of the obfuscation + if (garbage == null) + { + garbage = "0"; + } + + switch (lastGarbageArgumentType.toString()) + { + case "Z": + case "B": + case "C": + ins.add(new BiPush(instructions, Byte.parseByte(garbage))); + break; + case "S": + ins.add(new SiPush(instructions, Short.parseShort(garbage))); + break; + case "I": + ins.add(new LDC(instructions, Integer.parseInt(garbage))); + break; + case "D": + ins.add(new LDC(instructions, Double.parseDouble(garbage))); + break; + case "F": + ins.add(new LDC(instructions, Float.parseFloat(garbage))); + break; + case "J": + ins.add(new LDC(instructions, Long.parseLong(garbage))); + break; + default: + throw new RuntimeException("Unknown type"); + } + } + + if (invokeMethod.isStatic()) + { + ins.add(new InvokeStatic(instructions, invokeMethod.getPoolMethod())); + } + else + { + ins.add(new InvokeVirtual(instructions, invokeMethod.getPoolMethod())); + } + + Type returnValue = invokeMethod.getDescriptor().getReturnValue(); + InstructionType returnType; + + if (returnValue.isPrimitive() && returnValue.getDimensions() == 0) + { + switch (returnValue.toString()) + { + case "Z": + case "I": + returnType = InstructionType.IRETURN; + break; + case "J": + returnType = InstructionType.LRETURN; + break; + case "F": + returnType = InstructionType.FRETURN; + break; + case "D": + returnType = InstructionType.DRETURN; + break; + case "V": + returnType = InstructionType.RETURN; + break; + default: + assert false; + return; + } + } + else + { + returnType = InstructionType.ARETURN; + } + + ins.add(new Return(instructions, returnType)); + + clazz.addMethod(invokerMethodSignature); + } + + int getInjectedInvokers() + { + return injectedInvokers; + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectMojo.java b/injector-plugin/src/main/java/net/runelite/injector/InjectMojo.java new file mode 100644 index 0000000000..b7c22194fe --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectMojo.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.deob.clientver.ClientVersion; +import net.runelite.deob.util.JarUtil; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +@Mojo( + name = "runelite-injector", + defaultPhase = LifecyclePhase.GENERATE_RESOURCES +) +public class InjectMojo extends AbstractMojo +{ + private final Log log = getLog(); + @Parameter(defaultValue = "${project.build.outputDirectory}") + private File outputDirectory; + @Parameter(defaultValue = "./runescape-client/target/rs-client-${project.version}.jar", readonly = true, required = true) + private String rsClientPath; + @Parameter(defaultValue = "${net.runelite.rs:vanilla:jar}", readonly = true, required = true) + private String vanillaPath; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException + { + ClientVersion ver = new ClientVersion(new File(vanillaPath)); + int version; + try + { + version = ver.getVersion(); + } + catch (IOException ex) + { + throw new MojoExecutionException("Unable to read vanilla client version", ex); + } + + log.info("Vanilla client version " + version); + + ClassGroup rs; + ClassGroup vanilla; + + try + { + rs = JarUtil.loadJar(new File(rsClientPath)); + vanilla = JarUtil.loadJar(new File(vanillaPath)); + } + catch (IOException ex) + { + throw new MojoExecutionException("Unable to load dependency jars", ex); + } + + Injector injector = new Injector(rs, vanilla); + try + { + injector.inject(); + } + catch (InjectionException ex) + { + throw new MojoExecutionException("Error injecting client", ex); + } + + InjectorValidator iv = new InjectorValidator(vanilla); + iv.validate(); + + if (iv.getError() > 0) + { + throw new MojoExecutionException("Error building injected jar"); + } + + if (iv.getMissing() > 0) + { + throw new MojoExecutionException("Unable to inject all methods"); + } + + try + { + writeClasses(vanilla, outputDirectory); + } + catch (IOException ex) + { + throw new MojoExecutionException("Unable to write classes", ex); + } + + log.info("Injector wrote " + vanilla.getClasses().size() + " classes, " + iv.getOkay() + " injected methods"); + } + + private void writeClasses(ClassGroup group, File outputDirectory) throws IOException + { + for (ClassFile cf : group.getClasses()) + { + File classFile = getClassFile(outputDirectory, cf); + byte[] classData = JarUtil.writeClass(group, cf); + + try (FileOutputStream fout = new FileOutputStream(classFile, false)) + { + fout.write(classData); + } + } + } + + private File getClassFile(File base, ClassFile cf) + { + File f = base; + + String[] parts = cf.getName().split("/"); + for (int i = 0; i < parts.length - 1; ++i) + { + String part = parts[i]; + + f = new File(f, part); + } + + f.mkdirs(); + f = new File(f, parts[parts.length - 1] + ".class"); + + return f; + } + +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectSetter.java b/injector-plugin/src/main/java/net/runelite/injector/InjectSetter.java new file mode 100644 index 0000000000..58c225fa87 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectSetter.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.util.List; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LMul; +import net.runelite.asm.attributes.code.instructions.PutField; +import net.runelite.asm.attributes.code.instructions.PutStatic; +import net.runelite.asm.attributes.code.instructions.VReturn; +import net.runelite.asm.signature.Signature; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InjectSetter +{ + private static final Logger logger = LoggerFactory.getLogger(InjectSetter.class); + + private final Inject inject; + + private int injectedSetters; + + InjectSetter(Inject inject) + { + this.inject = inject; + } + + /** + * inject a setter into the vanilla classgroup + * + * @param targetClass Class where to inject the setter (field's class, + * or client) + * @param targetApiClass API targetClass implements, which may have the + * setter declared + * @param field Field of vanilla that will be set + * @param exportedName exported name of field + */ + void injectSetter(ClassFile targetClass, Class targetApiClass, Field field, String exportedName, Number setter) + { + java.lang.reflect.Method method = inject.findImportMethodOnApi(targetApiClass, exportedName, true); + if (method == null) + { + logger.warn("Setter injection for field {} but an API method was not found on {}", exportedName, targetApiClass); + return; + } + + if (method.getParameterCount() != 1) + { + logger.warn("Setter {} with not parameter count != 1?", exportedName); + return; + } + + logger.info("Injecting setter for {} on {}", exportedName, targetApiClass); + + assert targetClass.findMethod(method.getName()) == null; + assert field.isStatic() || field.getClassFile() == targetClass; + + Signature sig = new Signature.Builder() + .setReturnType(Type.VOID) + .addArgument(Inject.classToType(method.getParameterTypes()[0])) + .build(); + + Method setterMethod = new Method(targetClass, method.getName(), sig); + setterMethod.setAccessFlags(ACC_PUBLIC); + targetClass.addMethod(setterMethod); + ++injectedSetters; + + Code code = new Code(setterMethod); + setterMethod.setCode(code); + + Instructions instructions = code.getInstructions(); + List ins = instructions.getInstructions(); + + // load this + if (!field.isStatic()) + { + ins.add(new ALoad(instructions, 0)); + } + + // load argument + Type argumentType = sig.getTypeOfArg(0); + ins.add(inject.createLoadForTypeIndex(instructions, argumentType, 1)); + + // cast argument to field type + Type fieldType = field.getType(); + if (!argumentType.equals(fieldType)) + { + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(fieldType); + ins.add(checkCast); + } + + if (setter != null) + { + assert setter instanceof Integer || setter instanceof Long; + + if (setter instanceof Integer) + { + ins.add(new LDC(instructions, (int) setter)); + ins.add(new IMul(instructions)); + } + else + { + ins.add(new LDC(instructions, (long) setter)); + ins.add(new LMul(instructions)); + } + } + + if (field.isStatic()) + { + ins.add(new PutStatic(instructions, field)); + } + else + { + ins.add(new PutField(instructions, field)); + } + + ins.add(new VReturn(instructions)); + } + + int getInjectedSetters() + { + return injectedSetters; + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectUtil.java b/injector-plugin/src/main/java/net/runelite/injector/InjectUtil.java new file mode 100644 index 0000000000..2292b349ab --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectUtil.java @@ -0,0 +1,281 @@ +package net.runelite.injector; + +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.annotation.Annotation; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; + +public class InjectUtil +{ + public static ClassFile toObClass(final ClassGroup vanilla, final ClassFile deobCf) throws InjectionException + { + final String obfuscatedName = DeobAnnotations.getObfuscatedName(deobCf.getAnnotations()); + final ClassFile obCf = vanilla.findClass(obfuscatedName); + + if (obCf == null) + { + throw new InjectionException(String.format("ClassFile \"%s\" could not be found.", obfuscatedName)); + } + + return obCf; + } + + public static Field toObField(final ClassGroup vanilla, final Field field) throws InjectionException + { + String obfuscatedClassName = DeobAnnotations.getObfuscatedName(field.getClassFile().getAnnotations()); + String obfuscatedFieldName = DeobAnnotations.getObfuscatedName(field.getAnnotations()); // obfuscated name of field + Type type = getFieldType(field); + + ClassFile obfuscatedClass = vanilla.findClass(obfuscatedClassName); + if (obfuscatedClass == null) + { + throw new InjectionException(String.format("ClassFile \"%s\" could not be found.", obfuscatedClassName)); + } + + Field obfuscatedField = obfuscatedClass.findFieldDeep(obfuscatedFieldName, type); + if (obfuscatedField == null) + { + throw new InjectionException(String.format("Field \"%s\" could not be found.", obfuscatedFieldName)); + } + + return obfuscatedField; + } + + public static ClassFile toDeobClass(final ClassFile obCf, final ClassGroup deob) throws InjectionException + { + final ClassFile wowThatWasQuick = deob.findObfuscatedName(obCf.getName()); + if (wowThatWasQuick == null) + { + throw new InjectionException("It wasn't obfscated enough, or a bit too much. Whatever it was it, wasn't in deob"); + } + return wowThatWasQuick; + } + + public static Type getFieldType(final Field f) + { + Type type = f.getType(); + + Annotation obfSignature = f.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE); + if (obfSignature != null) + { + //Annotation exists. Type was updated by us during deobfuscation + type = DeobAnnotations.getObfuscatedType(f); + } + + return type; + } + + /** + * Find a static method in ClassGroup group. Check the class with name hint first. + * (useful for static methods which are in the class they belong to) + */ + public static Method findStaticMethod(final ClassGroup group, final String name, final String hint) throws InjectionException + { + final ClassFile cf = group.findClass(hint); + + if (cf == null) + { + throw new InjectionException(String.format("ClassFile \"%s\" could not be found.", hint)); + } + + Method m = cf.findStaticMethod(name); + + if (m == null) + { + m = group.findStaticMethod(name); + } + + return m; + } + + /** + * Find a static method in ClassGroup group. Throws exception if not found. + */ + public static Method findStaticMethod(final ClassGroup group, final String name) throws InjectionException + { + Method m = group.findStaticMethod(name); + + if (m == null) + { + throw new InjectionException(String.format("Static method \"%s\" could not be found.", name)); + } + + return m; + } + + /** + * Find a static method in ClassGroup group. Throws exception if not found. + */ + public static Method findStaticMethod(final ClassGroup group, final String name, Signature sig) throws InjectionException + { + Method m = group.findStaticMethod(name, sig); + + if (m == null) + { + throw new InjectionException(String.format("Static method \"%s\" could not be found.", name)); + } + + return m; + } + + public static Method findMethod(Inject inject, String name) throws InjectionException + { + return findMethod(inject, name, null); + } + + public static Method findMethod(Inject inject, String name, String hint) throws InjectionException + { + if (hint != null) + { + ClassFile c = inject.getDeobfuscated().findClass(hint); + + if (c == null) + { + throw new InjectionException("Class " + hint + " doesn't exist. (check capitalization)"); + } + + Method deob = c.findMethod(name); + + if (deob != null) + { + String obfuscatedName = DeobAnnotations.getObfuscatedName(deob.getAnnotations()); + Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(deob); + + ClassFile ob = toObClass(inject.getVanilla(), c); + + return ob.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : deob.getDescriptor()); + } + } + + for (ClassFile c : inject.getDeobfuscated().getClasses()) + { + for (Method m : c.getMethods()) + { + if (!m.getName().equals(name)) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations()); + Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(m); + + ClassFile c2 = toObClass(inject.getVanilla(), c); + + return c2.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : m.getDescriptor()); + } + } + + throw new InjectionException("Couldn't find method " + name); + } + + public static Method findStaticMethod(Inject inject, String name) throws InjectionException + { + for (ClassFile c : inject.getDeobfuscated().getClasses()) + { + for (Method m : c.getMethods()) + { + if (!m.isStatic() || !m.getName().equals(name)) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(m.getAnnotations()); + Signature obfuscatedSignature = DeobAnnotations.getObfuscatedSignature(m); + + ClassFile c2 = toObClass(inject.getVanilla(), c); + + return c2.findMethod(obfuscatedName, (obfuscatedSignature != null) ? obfuscatedSignature : m.getDescriptor()); + } + } + + throw new InjectionException("Couldn't find static method " + name); + } + + + public static Field findObField(Inject inject, String name) throws InjectionException + { + for (ClassFile c : inject.getVanilla().getClasses()) + { + for (Field f : c.getFields()) + { + if (!f.getName().equals(name)) + { + continue; + } + return f; + } + } + + throw new InjectionException(String.format("Field \"%s\" could not be found.", name)); + } + + public static Field findDeobField(Inject inject, String name) throws InjectionException + { + return findDeobField(inject, name, null); + } + + public static Field findDeobField(Inject inject, String name, String hint) throws InjectionException + { + if (hint != null) + { + ClassFile c = inject.getDeobfuscated().findClass(hint); + if (c == null) + { + throw new InjectionException("Class " + hint + " doesn't exist. (check capitalization)"); + } + + for (Field f : c.getFields()) + { + if (!f.getName().equals(name)) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(f.getAnnotations()); + + ClassFile c2 = toObClass(inject.getVanilla(), c); + return c2.findField(obfuscatedName); + } + } + + for (ClassFile c : inject.getDeobfuscated().getClasses()) + { + for (Field f : c.getFields()) + { + if (!f.getName().equals(name)) + { + continue; + } + + String obfuscatedName = DeobAnnotations.getObfuscatedName(f.getAnnotations()); + + ClassFile c2 = toObClass(inject.getVanilla(), c); + return c2.findField(obfuscatedName); + } + } + + throw new InjectionException(String.format("Mapped field \"%s\" could not be found.", name)); + } + + public static Field findDeobFieldButUseless(Inject inject, String name) throws InjectionException + { + for (ClassFile c : inject.getDeobfuscated().getClasses()) + { + for (Field f : c.getFields()) + { + if (!f.getName().equals(name)) + { + continue; + } + + return f; + } + } + + throw new InjectionException(String.format("Mapped field \"%s\" could not be found.", name)); + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectionException.java b/injector-plugin/src/main/java/net/runelite/injector/InjectionException.java new file mode 100644 index 0000000000..7881ba739e --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectionException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +public class InjectionException extends Exception +{ + public InjectionException(String message) + { + super(message); + } + + public InjectionException(Throwable cause) + { + super(cause); + } + + public InjectionException(String message, Throwable cause) + { + super(message, cause); + } + +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/Injector.java b/injector-plugin/src/main/java/net/runelite/injector/Injector.java new file mode 100644 index 0000000000..4543ed5b46 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/Injector.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.io.File; +import java.io.IOException; + +import com.google.common.io.Files; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.deob.util.JarUtil; + +public class Injector +{ + private final ClassGroup deobfuscated, vanilla; + + public Injector(ClassGroup deobfuscated, ClassGroup vanilla) + { + this.deobfuscated = deobfuscated; + this.vanilla = vanilla; + } + + public static void main(String[] args) throws IOException, InjectionException + { + if (args.length < 3) + { + System.exit(-1); + } + + ClassGroup deobfuscated = JarUtil.loadJar(new File(args[0])); + ClassGroup vanilla = JarUtil.loadJar(new File(args[1])); + + Injector u = new Injector( + deobfuscated, + vanilla + ); + u.inject(); + + InjectorValidator iv = new InjectorValidator(vanilla); + iv.validate(); + + u.save(new File(args[2])); + } + + public void inject() throws InjectionException + { + Inject instance = new Inject(deobfuscated, vanilla); + instance.run(); + } + + private void save(File out) throws IOException + { + out.mkdirs(); + for (ClassFile cf : vanilla.getClasses()) + { + File f = new File(out, cf.getClassName() + ".class"); + byte[] data = JarUtil.writeClass(vanilla, cf); + Files.write(data, f); + } + } + + +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/InjectorValidator.java b/injector-plugin/src/main/java/net/runelite/injector/InjectorValidator.java new file mode 100644 index 0000000000..a49df63a3a --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/InjectorValidator.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.signature.Signature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Verifies the injected jar is valid + * + * @author Adam + */ +class InjectorValidator +{ + private static final Logger logger = LoggerFactory.getLogger(InjectorValidator.class); + + private static final String API_PACKAGE_BASE = "net/runelite/rs/api/"; + + private final ClassGroup group; + + private int error, missing, okay; + + InjectorValidator(ClassGroup group) + { + this.group = group; + } + + void validate() + { + for (ClassFile cf : group.getClasses()) + { + validate(cf); + } + + logger.info("{} overridden methods, {} missing", okay, missing); + } + + private void validate(ClassFile cf) + { + // find methods of the interface not implemented in the class + for (net.runelite.asm.pool.Class clazz : cf.getInterfaces().getInterfaces()) + { + if (!clazz.getName().startsWith(API_PACKAGE_BASE)) + { + continue; + } + + Class c; + try + { + c = Class.forName(clazz.getName().replace('/', '.')); + } + catch (ClassNotFoundException ex) + { + logger.warn(null, ex); + continue; + } + + if (cf.isAbstract()) + { + // Abstract classes don't have to implement anything + continue; + } + + for (Method method : c.getMethods()) + { + if (method.isSynthetic() || method.isDefault()) + { + continue; + } + + // could check method signature here too but it is + // annoying to deal with both runelite api and java + // reflection api + if (cf.findMethodDeep(method.getName()) == null) + { + logger.warn("Class {} implements interface {} but not does implement method {}", + cf.getName(), c.getSimpleName(), method); + ++missing; + } + else + { + ++okay; + } + } + } + + Set signatures = new HashSet<>(); + + for (net.runelite.asm.Method method : cf.getMethods()) + { + NameAndSignature nas = new NameAndSignature(method.getName(), method.getDescriptor()); + + if (signatures.contains(nas)) + { + logger.error("Class {} has duplicate method with same name and signature {} {}", + cf.getName(), method.getName(), method.getDescriptor()); + ++error; + } + + signatures.add(nas); + } + } + + int getError() + { + return error; + } + + int getMissing() + { + return missing; + } + + int getOkay() + { + return okay; + } + + static final class NameAndSignature + { + String name; + Signature signature; + + NameAndSignature(String name, Signature signature) + { + this.name = name; + this.signature = signature; + } + + @Override + public int hashCode() + { + int hash = 3; + hash = 67 * hash + Objects.hashCode(this.name); + hash = 67 * hash + Objects.hashCode(this.signature); + return hash; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + final NameAndSignature other = (NameAndSignature) obj; + if (!Objects.equals(this.name, other.name)) + { + return false; + } + if (!Objects.equals(this.signature, other.signature)) + { + return false; + } + return true; + } + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/MixinInjector.java b/injector-plugin/src/main/java/net/runelite/injector/MixinInjector.java new file mode 100644 index 0000000000..bf4627c785 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/MixinInjector.java @@ -0,0 +1,998 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector; + +import com.google.common.reflect.ClassPath; +import com.google.common.reflect.ClassPath.ClassInfo; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import net.runelite.api.mixins.Mixin; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.annotation.Annotation; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.FieldInstruction; +import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction; +import net.runelite.asm.attributes.code.instruction.types.LVTInstruction; +import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction; +import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.ANewArray; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.GetField; +import net.runelite.asm.attributes.code.instructions.ILoad; +import net.runelite.asm.attributes.code.instructions.InvokeDynamic; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.Pop; +import net.runelite.asm.attributes.code.instructions.PutField; +import net.runelite.asm.signature.Signature; +import net.runelite.asm.visitors.ClassFileVisitor; +import net.runelite.deob.DeobAnnotations; +import static net.runelite.injector.InjectUtil.findStaticMethod; +import static net.runelite.injector.InjectUtil.toDeobClass; +import static net.runelite.injector.InjectUtil.toObClass; +import static net.runelite.injector.InjectUtil.toObField; +import org.objectweb.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MixinInjector +{ + private static final Logger logger = LoggerFactory.getLogger(MixinInjector.class); + + private static final Type INJECT = new Type("Lnet/runelite/api/mixins/Inject;"); + private static final Type SHADOW = new Type("Lnet/runelite/api/mixins/Shadow;"); + private static final Type COPY = new Type("Lnet/runelite/api/mixins/Copy;"); + private static final Type REPLACE = new Type("Lnet/runelite/api/mixins/Replace;"); + private static final Type FIELDHOOK = new Type("Lnet/runelite/api/mixins/FieldHook;"); + private static final Type METHODHOOK = new Type("Lnet/runelite/api/mixins/MethodHook;"); + private static final Type JAVAX_INJECT = new Type("Ljavax/inject/Inject;"); + private static final Type NAMED = new Type("Ljavax/inject/Named;"); + + private static final String MIXIN_BASE = "net.runelite.mixins"; + private static final String ASSERTION_FIELD = "$assertionsDisabled"; + + private final Inject inject; + + // field name -> Field of injected fields + private final Map injectedFields = new HashMap<>(); + // Use net.runelite.asm.pool.Field instead of Field because the pool version has hashcode implemented + private final Map shadowFields = new HashMap<>(); + + MixinInjector(Inject inject) + { + this.inject = inject; + } + + public void inject() throws InjectionException + { + ClassPath classPath; + + try + { + classPath = ClassPath.from(this.getClass().getClassLoader()); + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + + // key: mixin class + // value: mixin targets + Map, List> mixinClasses = new HashMap<>(); + + // Find mixins and populate mixinClasses + for (ClassInfo classInfo : classPath.getTopLevelClasses(MIXIN_BASE)) + { + Class mixinClass = classInfo.load(); + List mixinTargets = new ArrayList<>(); + + for (Mixin mixin : mixinClass.getAnnotationsByType(Mixin.class)) + { + Class implementInto = mixin.value(); + + ClassFile targetCf = inject.findVanillaForInterface(implementInto); + + if (targetCf == null) + { + throw new InjectionException("No class implements " + implementInto + " for mixin " + mixinClass); + } + + mixinTargets.add(targetCf); + } + + mixinClasses.put(mixinClass, mixinTargets); + } + + inject(mixinClasses); + } + + public void inject(Map, List> mixinClasses) throws InjectionException + { + injectFields(mixinClasses); + findShadowFields(mixinClasses); + + for (Class mixinClass : mixinClasses.keySet()) + { + try + { + for (ClassFile cf : mixinClasses.get(mixinClass)) + { + // Make a new mixin ClassFile copy every time, + // so they don't share Code references + ClassFile mixinCf = loadClass(mixinClass); + + injectMethods(mixinCf, cf, shadowFields); + } + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + } + + injectFieldHooks(mixinClasses); + injectMethodHooks(mixinClasses); + } + + /** + * Finds fields that are marked @Inject and inject them into the target + */ + private void injectFields(Map, List> mixinClasses) throws InjectionException + { + // Inject fields, and put them in injectedFields if they can be used by other mixins + for (Class mixinClass : mixinClasses.keySet()) + { + ClassFile mixinCf; + + try + { + mixinCf = loadClass(mixinClass); + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + + List targetCfs = mixinClasses.get(mixinClass); + + for (ClassFile cf : targetCfs) + { + for (Field field : mixinCf.getFields()) + { + // Always inject $assertionsEnabled if its missing. + if (ASSERTION_FIELD.equals(field.getName())) + { + if (cf.findField(ASSERTION_FIELD, Type.BOOLEAN) != null) + { + continue; + } + } + else + { + Annotation inject = field.getAnnotations().find(INJECT); + + if (inject == null) + { + continue; + } + } + + Field copy = new Field(cf, field.getName(), field.getType()); + copy.setAccessFlags(field.getAccessFlags()); + copy.setPublic(); + copy.setValue(field.getValue()); + + Annotation jInject = field.getAnnotations().find(JAVAX_INJECT); + if (jInject != null) + { + copy.getAnnotations().addAnnotation(jInject); + logger.info("Added javax inject to {}.{}", cf.getClassName(), copy.getName()); + + Annotation named = field.getAnnotations().find(NAMED); + if (named != null) + { + copy.getAnnotations().addAnnotation(named); + logger.info("Added javax named to {}.{}", cf.getClassName(), copy.getName()); + } + } + + cf.addField(copy); + + if (injectedFields.containsKey(field.getName()) && !field.getName().equals(ASSERTION_FIELD)) + { + java.util.logging.Logger.getAnonymousLogger().severe("Duplicate field : " + field.getName()); + throw new InjectionException("Injected field names must be globally unique"); + } + + injectedFields.put(field.getName(), copy); + } + } + + } + } + + /** + * Find fields which are marked @Shadow, and what they shadow + */ + private void findShadowFields(Map, List> mixinClasses) throws InjectionException + { + // Find shadow fields + // Injected static fields take precedence when looking up shadowed fields + for (Class mixinClass : mixinClasses.keySet()) + { + ClassFile mixinCf; + + try + { + mixinCf = loadClass(mixinClass); + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + + for (Field field : mixinCf.getFields()) + { + Annotation shadow = field.getAnnotations().find(SHADOW); + if (shadow != null) + { + if (!field.isStatic()) + { + throw new InjectionException("Can only shadow static fields"); + } + + String shadowName = shadow.getElement().getString(); // shadow this field + + Field injectedField = injectedFields.get(shadowName); + if (injectedField != null) + { + // Shadow a field injected by a mixin + shadowFields.put(field.getPoolField(), injectedField); + } + else + { + // Shadow a field already in the gamepack + Field shadowField = InjectUtil.findDeobFieldButUseless(inject, shadowName); + + if (shadowField == null) + { + throw new InjectionException("Shadow of nonexistent field " + shadowName); + } + + Field obShadow = toObField(inject.getVanilla(), shadowField); + assert obShadow != null; + shadowFields.put(field.getPoolField(), obShadow); + } + } + } + } + } + + private ClassFile loadClass(Class clazz) throws IOException + { + try (InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) + { + ClassReader reader = new ClassReader(is); + ClassFileVisitor cv = new ClassFileVisitor(); + + reader.accept(cv, 0); + + return cv.getClassFile(); + } + } + + private void injectMethods(ClassFile mixinCf, ClassFile cf, Map shadowFields) + throws InjectionException + { + // Keeps mappings between methods annotated with @Copy -> the copied method within the vanilla pack + Map copiedMethods = new HashMap<>(); + + // Handle the copy mixins first, so all other mixins know of the copies + for (Method method : mixinCf.getMethods()) + { + Annotation copyAnnotation = method.getAnnotations().find(COPY); + + if (copyAnnotation == null) + { + continue; + } + + String deobMethodName = (String) copyAnnotation.getElement().getValue(); + Method deobMethod; + if (method.isStatic()) + { + deobMethod = findStaticMethod(inject.getDeobfuscated(), deobMethodName, method.getDescriptor().rsApiToRsClient()); + } + else + { + ClassFile deobCf = toDeobClass(cf, inject.getDeobfuscated()); + deobMethod = deobCf.findMethod(deobMethodName, method.getDescriptor().rsApiToRsClient()); + } + + + if (deobMethod == null) + { + throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf); + } + + if (method.isStatic() != deobMethod.isStatic()) + { + throw new InjectionException("Mixin method " + method + " should be " + (deobMethod.isStatic() ? "static" : "non-static")); + } + + // Find the vanilla class where the method to copy is in + String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations()); + ClassFile obCf = inject.getVanilla().findClass(obClassName); + assert obCf != null : "unable to find vanilla class from obfuscated name " + obClassName; + + String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations()); + Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod); + + if (obMethodName == null) + { + obMethodName = deobMethod.getName(); + } + if (obMethodSignature == null) + { + obMethodSignature = deobMethod.getDescriptor(); + } + + Method obMethod = obCf.findMethod(obMethodName, obMethodSignature); + if (obMethod == null) + { + throw new InjectionException("Failed to find the ob method " + obMethodName + " for mixin " + mixinCf); + } + + if (method.getDescriptor().size() > obMethod.getDescriptor().size()) + { + throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method"); + } + + Method copy = new Method(cf, "copy$" + deobMethodName, obMethodSignature); + moveCode(copy, obMethod.getCode()); + copy.setAccessFlags(obMethod.getAccessFlags()); + copy.setPublic(); + copy.getExceptions().getExceptions().addAll(obMethod.getExceptions().getExceptions()); + copy.getAnnotations().getAnnotations().addAll(obMethod.getAnnotations().getAnnotations()); + cf.addMethod(copy); + + /* + If the desc for the mixin method and the desc for the ob method + are the same in length, assume that the mixin method is taking + care of the garbage parameter itself. + */ + boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size() + && deobMethod.getDescriptor().size() < obMethodSignature.size(); + copiedMethods.put(method.getPoolMethod(), new CopiedMethod(copy, hasGarbageValue)); + + logger.debug("Injected copy of {} to {}", obMethod, copy); + } + + // Handle the rest of the mixin types + for (Method method : mixinCf.getMethods()) + { + boolean isClinit = "".equals(method.getName()); + boolean isInit = "".equals(method.getName()); + boolean hasInject = method.getAnnotations().find(INJECT) != null; + + // You can't annotate clinit, so its always injected + if ((hasInject && isInit) || isClinit) + { + if (!"()V".equals(method.getDescriptor().toString())) + { + throw new InjectionException("Injected constructors cannot have arguments"); + } + + Method[] originalMethods = cf.getMethods().stream() + .filter(n -> n.getName().equals(method.getName())) + .toArray(Method[]::new); + // If there isn't a already just inject ours, otherwise rename it + // This is always true for + String name = method.getName(); + if (originalMethods.length > 0) + { + name = "rl$$" + (isInit ? "init" : "clinit"); + } + String numberlessName = name; + for (int i = 1; cf.findMethod(name, method.getDescriptor()) != null; i++) + { + name = numberlessName + i; + } + + Method copy = new Method(cf, name, method.getDescriptor()); + moveCode(copy, method.getCode()); + copy.setAccessFlags(method.getAccessFlags()); + copy.setPrivate(); + assert method.getExceptions().getExceptions().isEmpty(); + + // Remove the call to the superclass's ctor + if (isInit) + { + Instructions instructions = copy.getCode().getInstructions(); + ListIterator listIter = instructions.getInstructions().listIterator(); + for (; listIter.hasNext(); ) + { + Instruction instr = listIter.next(); + if (instr instanceof InvokeSpecial) + { + InvokeSpecial invoke = (InvokeSpecial) instr; + assert invoke.getMethod().getName().equals(""); + listIter.remove(); + int pops = invoke.getMethod().getType().getArguments().size() + 1; + for (int i = 0; i < pops; i++) + { + listIter.add(new Pop(instructions)); + } + break; + } + } + } + + setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods); + cf.addMethod(copy); + + // Call our method at the return point of the matching method(s) + for (Method om : originalMethods) + { + Instructions instructions = om.getCode().getInstructions(); + ListIterator listIter = instructions.getInstructions().listIterator(); + for (; listIter.hasNext(); ) + { + Instruction instr = listIter.next(); + if (instr instanceof ReturnInstruction) + { + listIter.previous(); + if (isInit) + { + listIter.add(new ALoad(instructions, 0)); + listIter.add(new InvokeSpecial(instructions, copy.getPoolMethod())); + } + else if (isClinit) + { + listIter.add(new InvokeStatic(instructions, copy.getPoolMethod())); + } + listIter.next(); + } + } + } + + logger.debug("Injected mixin method {} to {}", copy, cf); + } + else if (hasInject) + { + // Make sure the method doesn't invoke copied methods + for (Instruction i : method.getCode().getInstructions().getInstructions()) + { + if (i instanceof InvokeInstruction) + { + InvokeInstruction ii = (InvokeInstruction) i; + + if (copiedMethods.containsKey(ii.getMethod())) + { + throw new InjectionException("Injected methods cannot invoke copied methods"); + } + } + } + + Method copy = new Method(cf, method.getName(), method.getDescriptor()); + moveCode(copy, method.getCode()); + copy.setAccessFlags(method.getAccessFlags()); + copy.setPublic(); + assert method.getExceptions().getExceptions().isEmpty(); + + setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods); + + cf.addMethod(copy); + + logger.debug("Injected mixin method {} to {}", copy, cf); + } + else if (method.getAnnotations().find(REPLACE) != null) + { + Annotation replaceAnnotation = method.getAnnotations().find(REPLACE); + String deobMethodName = (String) replaceAnnotation.getElement().getValue(); + + ClassFile deobCf = inject.toDeobClass(cf); + Method deobMethod = findDeobMethod(deobCf, deobMethodName, method.getDescriptor()); + + if (deobMethod == null) + { + throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf); + } + + if (method.isStatic() != deobMethod.isStatic()) + { + throw new InjectionException("Mixin method " + method + " should be " + + (deobMethod.isStatic() ? "static" : "non-static")); + } + + String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations()); + Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod); + + // Deob signature is the same as ob signature + if (obMethodName == null) + { + obMethodName = deobMethod.getName(); + } + if (obMethodSignature == null) + { + obMethodSignature = deobMethod.getDescriptor(); + } + + // Find the vanilla class where the method to copy is in + String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations()); + ClassFile obCf = inject.getVanilla().findClass(obClassName); + + Method obMethod = obCf.findMethod(obMethodName, obMethodSignature); + assert obMethod != null : "obfuscated method " + obMethodName + obMethodSignature + " does not exist"; + + if (method.getDescriptor().size() > obMethod.getDescriptor().size()) + { + throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method"); + } + + Type returnType = method.getDescriptor().getReturnValue(); + Type deobReturnType = inject.apiTypeToDeobfuscatedType(returnType); + if (!returnType.equals(deobReturnType)) + { + ClassFile deobReturnTypeClassFile = inject.getDeobfuscated() + .findClass(deobReturnType.getInternalName()); + if (deobReturnTypeClassFile != null) + { + ClassFile obReturnTypeClass = toObClass(inject.getVanilla(), deobReturnTypeClassFile); + + Instructions instructions = method.getCode().getInstructions(); + ListIterator listIter = instructions.getInstructions().listIterator(); + for (; listIter.hasNext(); ) + { + Instruction instr = listIter.next(); + if (instr instanceof ReturnInstruction) + { + listIter.previous(); + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(new Type(obReturnTypeClass.getName())); + listIter.add(checkCast); + listIter.next(); + } + } + } + } + + moveCode(obMethod, method.getCode()); + + boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size() + && deobMethod.getDescriptor().size() < obMethodSignature.size(); + + if (hasGarbageValue) + { + int garbageIndex = obMethod.isStatic() + ? obMethod.getDescriptor().size() - 1 + : obMethod.getDescriptor().size(); + + /* + If the mixin method doesn't have the garbage parameter, + the compiler will have produced code that uses the garbage + parameter's local variable index for other things, + so we'll have to add 1 to all loads/stores to indices + that are >= garbageIndex. + */ + shiftLocalIndices(obMethod.getCode().getInstructions(), garbageIndex); + } + + setOwnersToTargetClass(mixinCf, cf, obMethod, shadowFields, copiedMethods); + + logger.debug("Replaced method {} with mixin method {}", obMethod, method); + } + } + } + + private void moveCode(Method method, Code code) + { + Code newCode = new Code(method); + newCode.setMaxStack(code.getMaxStack()); + newCode.getInstructions().getInstructions().addAll(code.getInstructions().getInstructions()); + // Update instructions for each instruction + for (Instruction i : newCode.getInstructions().getInstructions()) + { + i.setInstructions(newCode.getInstructions()); + } + newCode.getExceptions().getExceptions().addAll(code.getExceptions().getExceptions()); + for (net.runelite.asm.attributes.code.Exception e : newCode.getExceptions().getExceptions()) + { + e.setExceptions(newCode.getExceptions()); + } + method.setCode(newCode); + } + + private void setOwnersToTargetClass(ClassFile mixinCf, ClassFile cf, Method method, + Map shadowFields, + Map copiedMethods) + throws InjectionException + { + ListIterator iterator = method.getCode().getInstructions().getInstructions().listIterator(); + + while (iterator.hasNext()) + { + Instruction i = iterator.next(); + + if (i instanceof ANewArray) + { + Type type = ((ANewArray) i).getType_(); + ClassFile deobCf = inject.getDeobfuscated().findClass(type.toString().replace("Lnet/runelite/rs/api/RS", "").replace(";", "")); + + if (deobCf != null) + { + ClassFile obReturnTypeClass = toObClass(inject.getVanilla(), deobCf); + Type newType = new Type("L" + obReturnTypeClass.getName() + ";"); + + ((ANewArray) i).setType(newType); + logger.info("Replaced {} type {} with type {}", i, type, newType); + } + } + + if (i instanceof InvokeInstruction) + { + InvokeInstruction ii = (InvokeInstruction) i; + + CopiedMethod copiedMethod = copiedMethods.get(ii.getMethod()); + if (copiedMethod != null) + { + ii.setMethod(copiedMethod.obMethod.getPoolMethod()); + + // Pass through garbage value if the method has one + if (copiedMethod.hasGarbageValue) + { + int garbageIndex = copiedMethod.obMethod.isStatic() + ? copiedMethod.obMethod.getDescriptor().size() - 1 + : copiedMethod.obMethod.getDescriptor().size(); + + iterator.previous(); + iterator.add(new ILoad(method.getCode().getInstructions(), garbageIndex)); + iterator.next(); + } + } + else if (ii.getMethod().getClazz().getName().equals(mixinCf.getName())) + { + ii.setMethod(new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(cf.getName()), + ii.getMethod().getName(), + ii.getMethod().getType() + )); + } + } + else if (i instanceof FieldInstruction) + { + FieldInstruction fi = (FieldInstruction) i; + + Field shadowed = shadowFields.get(fi.getField()); + if (shadowed != null) + { + fi.setField(shadowed.getPoolField()); + } + else if (fi.getField().getClazz().getName().equals(mixinCf.getName())) + { + fi.setField(new net.runelite.asm.pool.Field( + new net.runelite.asm.pool.Class(cf.getName()), + fi.getField().getName(), + fi.getField().getType() + )); + } + } + else if (i instanceof PushConstantInstruction) + { + PushConstantInstruction pi = (PushConstantInstruction) i; + if (mixinCf.getPoolClass().equals(pi.getConstant())) + { + pi.setConstant(cf.getPoolClass()); + } + } + + verify(mixinCf, i); + } + } + + private void shiftLocalIndices(Instructions instructions, int startIdx) + { + for (Instruction i : instructions.getInstructions()) + { + if (i instanceof LVTInstruction) + { + LVTInstruction lvti = (LVTInstruction) i; + + if (lvti.getVariableIndex() >= startIdx) + { + lvti.setVariableIndex(lvti.getVariableIndex() + 1); + } + } + } + } + + private Method findDeobMethod(ClassFile deobCf, String deobMethodName, Signature descriptor) + throws InjectionException + { + List matchingMethods = new ArrayList<>(); + + for (Method m : deobCf.getMethods()) + { + if (!deobMethodName.equals(m.getName())) + { + continue; + } + + Type returnType = inject.apiTypeToDeobfuscatedType(descriptor.getReturnValue()); + Type returnType2 = m.getDescriptor().getReturnValue(); + + if (!returnType.equals(returnType2)) + { + continue; + } + + List args = descriptor.getArguments(); + List args2 = m.getDescriptor().getArguments(); + + if (args.size() > args2.size()) + { + continue; + } + + boolean matchingArgs = true; + + for (int i = 0; i < args.size(); i++) + { + Type type = inject.apiTypeToDeobfuscatedType(args.get(i)); + Type type2 = args2.get(i); + + if (!type.equals(type2)) + { + matchingArgs = false; + break; + } + } + + if (!matchingArgs) + { + continue; + } + + matchingMethods.add(m); + } + + if (matchingMethods.size() > 1) + { + // this happens when it has found several deob methods for some mixin method, + // to get rid of the error, refine your search by making your mixin method have more parameters + throw new InjectionException("There are several matching methods when there should only be one"); + } + else if (matchingMethods.size() == 1) + { + return matchingMethods.get(0); + } + + Method method = deobCf.findMethod(deobMethodName); + + if (method == null) + { + // Look for static methods if an instance method couldn't be found + for (ClassFile deobCf2 : inject.getDeobfuscated().getClasses()) + { + if (deobCf2 != deobCf) + { + method = deobCf2.findMethod(deobMethodName); + + if (method != null) + { + break; + } + } + } + } + + return method; + } + + private void verify(ClassFile mixinCf, Instruction i) throws InjectionException + { + if (i instanceof FieldInstruction) + { + FieldInstruction fi = (FieldInstruction) i; + + if (fi.getField().getClazz().getName().equals(mixinCf.getName())) + { + if (i instanceof PutField || i instanceof GetField) + { + throw new InjectionException("Access to non static member field of mixin"); + } + + Field field = fi.getMyField(); + if (field != null && !field.isPublic()) + { + throw new InjectionException("Static access to non public field " + field); + } + } + } + else if (i instanceof InvokeStatic) + { + InvokeStatic is = (InvokeStatic) i; + + if (is.getMethod().getClazz() != mixinCf.getPoolClass() + && is.getMethod().getClazz().getName().startsWith(MIXIN_BASE.replace(".", "/"))) + { + throw new InjectionException("Invoking static methods of other mixins is not supported"); + } + } + else if (i instanceof InvokeDynamic) + { + // RS classes don't verify under java 7+ due to the + // super() invokespecial being inside of a try{} + throw new InjectionException("Injected bytecode must be Java 6 compatible"); + } + } + + private void injectFieldHooks(Map, List> mixinClasses) throws InjectionException + { + InjectHook injectHook = new InjectHook(inject); + + for (Class mixinClass : mixinClasses.keySet()) + { + ClassFile mixinCf; + + try + { + mixinCf = loadClass(mixinClass); + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + + List targetCfs = mixinClasses.get(mixinClass); + + for (ClassFile cf : targetCfs) + { + for (Method method : mixinCf.getMethods()) + { + Annotation fieldHook = method.getAnnotations().find(FIELDHOOK); + if (fieldHook != null) + { + String hookName = fieldHook.getElement().getString(); + boolean before = fieldHook.getElements().size() == 2 && fieldHook.getElements().get(1).getValue().equals(true); + ClassFile deobCf = inject.toDeobClass(cf); + Field targetField = deobCf.findField(hookName); + if (targetField == null) + { + // first try non static fields, then static + targetField = InjectUtil.findDeobFieldButUseless(inject, hookName); + } + + if (targetField == null) + { + throw new InjectionException("Field hook for nonexistent field " + hookName + " on " + method); + } + + Annotation an = targetField.getAnnotations().find(DeobAnnotations.OBFUSCATED_GETTER); + Number getter = null; + if (an != null) + { + getter = (Number) an.getElement().getValue(); + } + + Field obField = toObField(inject.getVanilla(), targetField); + + if (method.isStatic() != targetField.isStatic()) + { + throw new InjectionException("Field hook method static flag must match target field"); + } + + // cf is the target class to invoke + InjectHook.HookInfo hookInfo = new InjectHook.HookInfo(); + hookInfo.clazz = cf.getName(); + hookInfo.fieldName = hookName; + hookInfo.method = method; + hookInfo.before = before; + hookInfo.getter = getter; + injectHook.hook(obField, hookInfo); + } + } + } + } + + injectHook.run(); + + logger.info("Injected {} field hooks", injectHook.getInjectedHooks()); + } + + private void injectMethodHooks(Map, List> mixinClasses) throws InjectionException + { + InjectHookMethod injectHookMethod = new InjectHookMethod(inject); + + for (Class mixinClass : mixinClasses.keySet()) + { + ClassFile mixinCf; + + try + { + mixinCf = loadClass(mixinClass); + } + catch (IOException ex) + { + throw new InjectionException(ex); + } + + List targetCfs = mixinClasses.get(mixinClass); + + for (ClassFile cf : targetCfs) + { + for (Method method : mixinCf.getMethods()) + { + Annotation methodHook = method.getAnnotations().find(METHODHOOK); + + if (methodHook == null) + { + continue; + } + + String hookName = methodHook.getElement().getString(); + boolean end = methodHook.getElements().size() == 2 && methodHook.getElements().get(1).getValue().equals(true); + ClassFile deobCf = inject.toDeobClass(cf); + Method targetMethod = findDeobMethod(deobCf, hookName, method.getDescriptor()); + + if (targetMethod == null) + { + throw new InjectionException("Method hook for nonexistent method " + hookName + " on " + method); + } + + if (method.isStatic() != targetMethod.isStatic()) + { + throw new InjectionException("Method hook static flag must match target - " + hookName); + } + + injectHookMethod.inject(method, targetMethod, hookName, end, false); + } + } + } + } + + private static class CopiedMethod + { + private Method obMethod; + private boolean hasGarbageValue; + + private CopiedMethod(Method obMethod, boolean hasGarbageValue) + { + this.obMethod = obMethod; + this.hasGarbageValue = hasGarbageValue; + } + } +} diff --git a/injector-plugin/src/main/java/net/runelite/injector/raw/ClearColorBuffer.java b/injector-plugin/src/main/java/net/runelite/injector/raw/ClearColorBuffer.java new file mode 100644 index 0000000000..f4de5d056e --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/raw/ClearColorBuffer.java @@ -0,0 +1,124 @@ +package net.runelite.injector.raw; + +import java.util.ListIterator; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ILoad; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.pool.Class; +import net.runelite.asm.signature.Signature; +import net.runelite.injector.Inject; +import net.runelite.injector.InjectUtil; +import net.runelite.injector.InjectionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ClearColorBuffer +{ + private static final Logger log = LoggerFactory.getLogger(ClearColorBuffer.class); + private static final net.runelite.asm.pool.Method clearBuffer = new net.runelite.asm.pool.Method( + new Class("net.runelite.client.callback.Hooks"), + "clearColorBuffer", + new Signature("(IIIII)V") + ); + private final Inject inject; + + public ClearColorBuffer(Inject inject) + { + this.inject = inject; + } + + public void inject() throws InjectionException + { + injectColorBufferHooks(); + } + + private void injectColorBufferHooks() throws InjectionException + { + net.runelite.asm.pool.Method fillRectangle = InjectUtil.findStaticMethod(inject, "Rasterizer2D_fillRectangle").getPoolMethod(); + + int count = 0; + int replaced = 0; + + for (ClassFile cf : inject.getVanilla().getClasses()) + { + for (Method m : cf.getMethods()) + { + if (!m.isStatic()) + { + continue; + } + + Code c = m.getCode(); + if (c == null) + { + continue; + } + + Instructions ins = c.getInstructions(); + ListIterator it = ins.getInstructions().listIterator(); + + for (; it.hasNext(); ) + { + Instruction i = it.next(); + if (!(i instanceof InvokeStatic)) + { + continue; + } + + if (!((InvokeStatic) i).getMethod().equals(fillRectangle)) + { + continue; + } + + int indexToReturnTo = it.nextIndex(); + count++; + it.previous(); + Instruction current = it.previous(); + if (current instanceof LDC && ((LDC) current).getConstantAsInt() == 0) + { + int varIdx = 0; + for (; ; ) + { + current = it.previous(); + if (current instanceof ILoad && ((ILoad) current).getVariableIndex() == 3 - varIdx) + { + varIdx++; + log.debug(varIdx + " we can count yay"); + continue; + } + + break; + } + + if (varIdx == 4) + { + for (; !(current instanceof InvokeStatic); ) + { + current = it.next(); + } + assert it.nextIndex() == indexToReturnTo; + + it.set(new InvokeStatic(ins, clearBuffer)); + replaced++; + log.debug("Found drawRectangle at {}. Found: {}, replaced {}", m.getName(), count, replaced); + } + else + { + log.debug("Welp, guess this wasn't it chief " + m); + } + } + + while (it.nextIndex() != indexToReturnTo) + { + it.next(); + } + } + } + } + } +} \ No newline at end of file diff --git a/injector-plugin/src/main/java/net/runelite/injector/raw/DrawAfterWidgets.java b/injector-plugin/src/main/java/net/runelite/injector/raw/DrawAfterWidgets.java new file mode 100644 index 0000000000..5b6f6fbd50 --- /dev/null +++ b/injector-plugin/src/main/java/net/runelite/injector/raw/DrawAfterWidgets.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018, Lotto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.injector.raw; + +import java.util.HashSet; +import java.util.ListIterator; +import java.util.Set; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.Label; +import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction; +import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction; +import net.runelite.asm.attributes.code.instructions.GetStatic; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.signature.Signature; +import net.runelite.injector.Inject; +import static net.runelite.injector.InjectHookMethod.HOOKS; +import static net.runelite.injector.InjectUtil.findStaticMethod; +import net.runelite.injector.InjectionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DrawAfterWidgets +{ + private static final Logger logger = LoggerFactory.getLogger(DrawAfterWidgets.class); + + private final Inject inject; + + public DrawAfterWidgets(Inject inject) + { + this.inject = inject; + } + + public void inject() throws InjectionException + { + injectDrawAfterWidgets(); + } + + private void injectDrawAfterWidgets() throws InjectionException + { + /* + This call has to be injected using raw injection because the + drawWidgets method gets inlined in some revisions. If it wouldn't be, + mixins would be used to add the call to the end of drawWidgets. + + --> This hook depends on the positions of "if (535573958 * kl != -1)" and "jz.db();". + + + Revision 180 - client.gs(): + ______________________________________________________ + + @Export("drawLoggedIn") + final void drawLoggedIn() { + if(rootInterface != -1) { + ClientPreferences.method1809(rootInterface); + } + + int var1; + for(var1 = 0; var1 < rootWidgetCount; ++var1) { + if(__client_od[var1]) { + __client_ot[var1] = true; + } + + __client_oq[var1] = __client_od[var1]; + __client_od[var1] = false; + } + + __client_oo = cycle; + __client_lq = -1; + __client_ln = -1; + UserComparator6.__fg_jh = null; + if(rootInterface != -1) { + rootWidgetCount = 0; + Interpreter.method1977(rootInterface, 0, 0, SoundCache.canvasWidth, Huffman.canvasHeight, 0, 0, -1); + } + + < -- here appearantly + + Rasterizer2D.Rasterizer2D_resetClip(); + ______________________________________________________ + */ + + boolean injected = false; + + Method noClip = findStaticMethod(inject, "Rasterizer2D_resetClip"); // !!!!! + + if (noClip == null) + { + throw new InjectionException("Mapped method \"Rasterizer2D_resetClip\" could not be found."); + } + + net.runelite.asm.pool.Method poolNoClip = noClip.getPoolMethod(); + + for (ClassFile c : inject.getVanilla().getClasses()) + { + for (Method m : c.getMethods()) + { + if (m.getCode() == null) + { + continue; + } + + Instructions instructions = m.getCode().getInstructions(); + + Set