Compare commits

...

1643 Commits

Author SHA1 Message Date
Graham 8a7ca33aa4 Fix CacheDownloader locking up indefinitely if connect() fails 1 week ago
Graham 8ef3ef0ba5 Fix Js5MasterIndex creation if the LENGTHS format is used 4 months ago
Graham 1bb370fd60 Convert IntValueSet.Unknown to a data object 4 months ago
Graham 65de30ca58 Change 'Transforming client' log message to 'Transforming' 4 months ago
Graham e6f7a40f6e Make enabled transformers and their order configurable in profile 4 months ago
Graham d38ca044ec Use Kotlin's emptySet() method 4 months ago
Graham 223c451f2d Add MultipleAssignmentTransformer 4 months ago
Graham 6a7a29c85c Exclude overlapping initializers in StaticFieldUnscrambler 4 months ago
Graham 293bf83e30 Improve initializer extraction in StaticFieldUnscrambler 4 months ago
Graham b1ddd98187 Remove unused constants 4 months ago
Graham 9e001e32e1 Separate ZKM and Jagex tracing exception transformers 4 months ago
Graham 514799920e Run ExceptionTracingTransformer until we reach a fixed point 4 months ago
Graham 0cd8be87ca Add support for JOGL artifact links in early HD era loaders 4 months ago
Graham 7925be5301 Update dependencies 4 months ago
Graham f3b7b8d07a Remove urgent requests from prefetch queue 4 months ago
Graham b4b7f891c6 Update dependencies 7 months ago
Graham 2aff6e56b7 Extract links from pre-HD new engine loaders 7 months ago
Graham 71c6f5821b Log IDs of artifacts as they are refreshed 7 months ago
Graham 6fdf31594d Format WorldListResponseCodec 7 months ago
Graham bd36b77e44 Use getArgumentExpressions in HighDpiTransformer 7 months ago
Graham b0f37cd959 Add method for extracting arguments passed to an invoke instruction 7 months ago
Graham 85b1d52a5c Include last instruction in expression in getExpression output 7 months ago
Graham 1083839720 Add --skip-errors flag to client import command 7 months ago
Graham 774a92b289 De-duplicate artifact sources 7 months ago
Graham afdd7b5eea Update dependencies 7 months ago
Graham fd84c95203 Update Gradle 7 months ago
Graham d9f91bc0e7 Add file name and timestamp to artifact_sources table 7 months ago
Graham fe5cde0290 Add loginapplet and passapplet support 7 months ago
Graham ca3a7645b7 Add link to RS-Hacking 7 months ago
Graham 32daae09a8 Parse signlink build in clients as well as loaders 7 months ago
Graham 67762dd226 List sources on each artifact page 7 months ago
Graham 3932debd92 Add artifact_sources table 7 months ago
Graham 3da5bb0786 Allow unlimited reconnections if we make progress 7 months ago
Graham 856696a703 Reconnect in JS5 downloader if any I/O error occurs 7 months ago
Graham 4f5709acfa Add support for dedicated activity flag 7 months ago
Graham 2a0401a35e Add Mach-O support 7 months ago
Graham f6324198ef Add support for OSRS beta caches and server_version param 7 months ago
Graham 8052561dcf Improve server_version support 7 months ago
Graham 1f83b8668d Fix detection of build 503 7 months ago
Graham 5b44a4bca6 Improve client build number matching 7 months ago
Graham 6412b70343 Remove Discord discriminator 7 months ago
Graham b56092585c Add link to Moparisthebest's archive 7 months ago
Graham 6fe155540a Convert namePointer from RVA to raw data pointer 7 months ago
Graham 6b18461297 Fix games_id_seq value in V10__variants.sql migration 7 months ago
Graham e673f539af Update dependencies 7 months ago
Graham 73e959a3cb Add initial support for archiving clients 7 months ago
Graham c65cc2ff59 Allow server_version from jav_config to override build number 7 months ago
Graham ba72e14313 Add manual source_type 7 months ago
Graham 84ac2a9985 Update dependencies 8 months ago
Graham db07c93471 Add CabLibraryReader 8 months ago
Graham 2b086a1f8c Remove PF4J 8 months ago
Graham 5e5632637f Fix path in publishDokka task 8 months ago
Graham 6923004f07 Re-add support for deploying with CI 8 months ago
Graham 83074750bd Replace Drone badge with GitHub Actions 8 months ago
Graham fc6b49a7a2 Add initial GitHub Actions workflow 8 months ago
Graham 7ba6433f8b Remove Drone configuration 8 months ago
Graham e92cd6b993 Rename resizable variable 8 months ago
Graham 71b1ac8e20 Rename XteaKey to SymmetricKey 8 months ago
Graham 4ac91e4f29 Move XteaKey to its own file 8 months ago
Graham 1771f86d96 Add missing Message: prefix to login response message documentation 8 months ago
Graham e3a73291a9 Verify usernameHash and serverKey match 8 months ago
Graham 6d43e0392e Add initial GAMELOGIC packet support 8 months ago
Graham 508d9526ea Ignore ktlint rules we're not compatible with yet 8 months ago
Graham d85c01d045 Remove extraneous semicolon 8 months ago
Graham 0b6610c062 Replace use of deprecated static/resources methods 8 months ago
Graham 651b2f8b5f Update Guice 8 months ago
Graham e62941b00a Update Clikt 8 months ago
Graham dd7d7aac48 Update Kotlin 8 months ago
Graham c9fdaad285 Update SQLite 8 months ago
Graham 7193f59e71 Refactor Loc-related code 8 months ago
Graham c53d072f7a Update Kotlinter 8 months ago
Graham fb9cb2a6b1 Replace use of deprecated buildDir property 8 months ago
Graham 49d5c003d8 Update Gradle 8 months ago
Graham 92d61dc3b7 Update dependencies 8 months ago
Graham 68a6d7c76c Add expected/actual lengths to Js5Compression error messages 8 months ago
Graham cc3aa0aa60 Preserve timestamps in unpack200 command 9 months ago
Graham 483a8a0f16 Support arrays larger than 2**29 in Whirlpool implementation 9 months ago
Graham bba220aebc Add SHA-1 extension method to ByteBuf 9 months ago
Graham 195ffa7967 Add ZipInputStreamExtensions 9 months ago
Graham dfb1f3c0e6 Skip containers that already exist during import 9 months ago
Graham 28a9667471 Add support for resolving files/groups between old and new engine caches 10 months ago
Graham 97b53c5695 Remove use of wildcard import 10 months ago
Graham a5dfbdd691 Remove PolarKeyDownloader 10 months ago
Graham 5b8cd2964f Download names from Pazaz's repository 10 months ago
Graham 8cd73a926b Remove redundant type parameter 1 year ago
Graham 69ea1ac7ab Add beta HTTP js5 endpoint 1 year ago
Graham 9463a70520 Catch KeyDownloader::getMissingUrls exceptions 1 year ago
Graham 33ecd68654 Simplify CORS handling 1 year ago
Graham ddfc472c84 Switch to Ktor's Jetty backend 1 year ago
Graham bf30f1e4a5 Update dependencies 1 year ago
Graham 6c52f5f48f Update Kotlinter 1 year ago
Graham 6d0f28d0fa Update dependencies 1 year ago
Graham 5604811e8b Update Kotlin 1 year ago
Graham c13f131c32 Update Gradle 1 year ago
Graham d463ffa4d7 Update dependencies 1 year ago
Graham 962716524e Fix lint error 1 year ago
Graham 7d51312e57 Revert "Enforce consistent version of Kotlin's stdlib across the whole project" 1 year ago
Graham 54f2a44eab Move assignment outside if 1 year ago
Graham 12eba96055 Update copyright year 1 year ago
Graham ba5c285a47 Remove defunct OpenOSRS key downloader and link 1 year ago
Graham ea9ec62e6e Add support for fetching master index from the API 1 year ago
Graham 9d3282ca3a Simplify reference counting in Js5Service 1 year ago
Graham 62df015ad5 Always sort empty rows to the bottom of the table 2 years ago
Graham 55072a5102 Sort build column numerically 2 years ago
Graham 97ca5cbc2f Update dependencies 2 years ago
Graham 4ef349d0ac Add CoroutineExceptionHandler to LoginChannelHandler 2 years ago
Graham 0814443bc5 Add total size of all caches to the caches page 2 years ago
Graham 2d7b235f15 Add support for the new OSRS short code map format 2 years ago
Graham 2c70a7c1ec Add methods to allow reading files by group name and file ID 2 years ago
Graham c1704fc111 Exclude Logback from minimization 2 years ago
Graham 8f4d28393e Download XTEA keys from HDOS 2 years ago
Graham db1ecf3c00 Read after sending ExchangeSessionKey to the client 2 years ago
Graham 655b9c9cf7 Update dependencies 2 years ago
Graham ba0bd2ca3a Use 4 space indents in .sql files 2 years ago
Graham eebb54dd60 Disable formatting in all migrations 2 years ago
Graham 4ca7fab636 Add blank line between is blocks 2 years ago
Graham c0056f9cb1 Update kotlinter 2 years ago
Graham d63af29679 Fix linter error 2 years ago
Graham fd2545bc9d Format OpenNxtStore 2 years ago
Graham dc8fcd09f6 Flesh out LoginChannelHandler 2 years ago
Graham cf7c05441c Update dependencies 2 years ago
Graham 091c8ee1ca Update dependencies 2 years ago
Graham d0a46dc5e5 Removing loading requirements from the NXT downloader 2 years ago
Graham 4cc83e6316 Mark all methods in a final class as non-final 2 years ago
Graham aff58e5e73 Split FinalTransformer into Final{Class,Method}Transformer 2 years ago
Graham 39d2f18cca Add tool for unpacking OpenNXT caches 2 years ago
Graham dbb30e0bd8 Disable fsync in FlatFileStore 2 years ago
Graham 4aa181b91a Add support for disabling fsync in atomicWrite 2 years ago
Graham 4252bf0dbc Update Gradle 2 years ago
Graham c3c240b4e6 Cache the /caches.json endpoint for 15 minutes 2 years ago
Graham ef2919761d Add method for peeking at the version trailer 2 years ago
Graham 5ac5ae76f3 Update dependencies 2 years ago
Graham 5e4305b0f3 Refactor more code 2 years ago
Graham d80de942e0 Fix womanWear1/2 naming 2 years ago
Graham 0d87057ae6 Rename more classes 2 years ago
Graham 3841d39fe8 Refactor more TextureOps 2 years ago
Graham e2ceef0a32 Fix case 2 years ago
Graham 2abd1d7ea0 Fix CreateAccountCodec padding 2 years ago
Graham 76e7e93f3c Allocate buffer exactly in NameSuggestionsCodec 2 years ago
Graham fa41b48f1a Add all create responses 2 years ago
Graham f31b2519f9 Add all login responses except OK 2 years ago
Graham 827e6262a9 Rename 'Display video advertisement' to 'Show video advertisement' 2 years ago
Graham e84a58a36b Reformat tables in the glossary 2 years ago
Graham 4e6f5c360d Add StaffModLevel to the glossary 2 years ago
Graham 0a5e2343c1 Disambiguate create invalid password responses 2 years ago
Graham 851ef8e4e9 Separate LoginResponse and Js5LoginResponse 2 years ago
Graham e4b5f8b850 Check there are no trailing bytes in Rs2Decoder 2 years ago
Graham 0a99813932 Don't use LocalDate to represent date of birth in packets 2 years ago
Graham 650e298bc9 Add CREATE_ACCOUNT packet 2 years ago
Graham fef6441889 Fix encryption of CHECK_WORLD_SUITABILITY packet 2 years ago
Graham 72e259c8ad Fix length of CREATE_ACCOUNT packet 2 years ago
Graham 1bb244b7f7 Move length encoding/decoding from Rs2{Decoder,Encoder} to PacketCodec 2 years ago
Graham 431685124a Escape greater/less than symbols incorrectly interpreted as tags 2 years ago
Graham 6e41863c58 Add create protocol documentation 2 years ago
Graham cc8193eca4 Fix typo 2 years ago
Graham 02d39297cb Sort protocols 2 years ago
Graham 892a69df03 Fix cell alignment 2 years ago
Graham 537b158928 Add CREATE_CHECK_NAME packet 2 years ago
Graham 7c6ccbf556 Add CreateCheckDateOfBirthCountry packet 2 years ago
Graham 9e969d8dfa Add CHECK_WORLD_SUITABILITY packet 2 years ago
Graham 4c309a0f50 Split protocol packages into upstream/downstream packages 2 years ago
Graham 73defefef4 Create codecs with dependency injection 2 years ago
Graham af3477776c Fix ktor dependency names 2 years ago
Graham 55ecf7a037 Add missing Guice API dependency to the archive module 2 years ago
Graham 48944d6bac Update login packet field descriptions based on official packet names 2 years ago
Graham c01933614c Bind Jackson module consistently with other Guice modules 2 years ago
Graham 4604bc8b81 Add assisted injection extension 2 years ago
Graham 3fe7bdcedc Optimise CloseableInjector 2 years ago
Graham c43d48f71b Rename LOGIN_DOWNSTREAM_JS5REMOTE to JS5REMOTE_DOWNSTREAM 2 years ago
Graham 92c41d4a19 Update dependencies 2 years ago
Graham 80551adeef Improve consistency of protocol documentation 2 years ago
Graham 0666df686c Add Base37 implementation 2 years ago
Graham 0c2108d750 Add separate Protocol for INIT_JS5REMOTE_CONNECTION responses 2 years ago
Graham e90513aa36 Update to Ktor 2 2 years ago
Graham b665b9a359 Replace TODO with "Verify ID" in the login protocol documentation 2 years ago
Graham 4254b34b7d Update Gradle 2 years ago
Graham a590a80190 Add list of all game packets 2 years ago
Graham 3e39875b8c Refactor more code 2 years ago
Graham 67f3dbaf57 Update dependencies 2 years ago
Graham 58c943e9e6 Add missing thousands separator in coordinate system documentation 2 years ago
Graham ab248bb267 Fix small mistake in coordinate system documentation 2 years ago
Graham 4eea6d752a Don't terminate if downloading keys from a source fails 2 years ago
Graham e804fdc065 Add Cache-Control and ETag headers to the exportGroup endpoint 2 years ago
Graham 9c9a1ecf39 Add archive API documentation 2 years ago
Graham 617263f064 Update dependencies 2 years ago
Graham 21560b1afd Ignore fsync on directory failures 2 years ago
Graham f7e194dfa6 Improve atomicWrite 2 years ago
Graham 2292b9449c Ensure flow obstructor initializers always read a static field 2 years ago
Graham 2b720af6e5 Fix line length 2 years ago
Graham 3e2d6ee0ec Fix right-aligned columns 2 years ago
Graham 0158bc937b Add archive/index sizes and completion percentages to legacy cache pages 2 years ago
Graham aa316e273d Split version_list_stats table by index 2 years ago
Graham ee61999c6f Ensure size and block columns in index_stats are always non-NULL 2 years ago
Graham 6f30aebb22 Improve index table row switch order 2 years ago
Graham c85e8ed873 Remove unused import 2 years ago
Graham 2aaa19e05e Format CacheExporter 2 years ago
Graham f5b9f269f6 Add per-archive stats to the cache pages 2 years ago
Graham ce9604a28d Allow cross-origin requests to the archive 2 years ago
Graham c94678c7c5 Add API for downloading individual groups 2 years ago
Graham e5512cbdf6 Update number of loading requirements 2 years ago
Graham c3383aed11 Fix index_stats rows for empty indexes 2 years ago
Graham 339f1d504b Update dependencies 2 years ago
Graham ba573312dd Add missing JOIN condition to index_stats view 2 years ago
Graham d422934661 Add Jagex loc shape names to the glossary 2 years ago
Graham 3eefa3df52 Fix downloading caches 2 years ago
Graham af918cf535 Add support for hiding broken caches 2 years ago
Graham 80dda3f2dc Add missing JOIN condition 2 years ago
Graham d186f5aef4 Add initial support for separate scopes to the archiving service 2 years ago
Graham 2c31776c54 Update dependencies 2 years ago
Graham 7abb995461 Update Kotlin 2 years ago
Graham 88ef8aec92 Fix reading timestamps with sign bit set 2 years ago
Graham ddecca5d0b Update dependencies 2 years ago
Graham 804ae70def Format refreshViews() in CacheImporter 2 years ago
Graham fe69594180 Add command for extracting caches found with Edward's cache finder 2 years ago
Graham 03e6c3dd81 Annotate crypto methods with @Jvm{Overloads,Static} 2 years ago
Graham 26651618ef Optimise uncompression of encrypted groups with invalid keys 2 years ago
Graham caaddad0ed Flush underlying Store when a Cache is flushed 2 years ago
Graham b320348ec7 Fix bug where music data file was closed on flush 2 years ago
Graham bcc2fdf48e Add @JvmStatic and @JvmOverloads to more cache methods 2 years ago
Graham bf12f41faf Update run configurations 2 years ago
Graham 6f7350afa3 Fix JagArchive tests if libbzip2 is available 2 years ago
Graham 078b1f6197 Fix version trailers in RuneLiteStore 2 years ago
Graham 52ca09e4d0 Exclude JNR from minimization 2 years ago
Graham b7be7950c6 Update GeneratedByteBufExtensions 2 years ago
Graham 7859b929ad Fix buffer-generator mainClass name 2 years ago
Graham 4e19879ee0 Fix use of deprecated method in ByteBufExtensionGenerator 2 years ago
Graham 712d874848 Fix Jackson version 2 years ago
Graham a885695fdf Update dependencies 2 years ago
Graham 3e60a82ca1 Add command for unpacking RuneLite flatcaches 2 years ago
Graham ba75306169 Annotate cache methods with default parameters with @JvmOverloads 2 years ago
Graham aa2784a9e6 Add class for converting RuneLite flatcaches to other formats 2 years ago
Graham 8c415023af Update Gradle 2 years ago
Graham f7abf23dee Update dependencies 2 years ago
Graham c21895f052 Add JNR-based bzip2 implementation compatible with Jagex's 2 years ago
Graham a6dbb29ea0 Fix dependency order 2 years ago
Graham c9f397759e Use advisory locks to prevent concurrent view refreshes 2 years ago
Graham 35f54fd753 Skip corrupt archives when importing legacy caches 2 years ago
Graham 29716379c3 Update Gradle 2 years ago
Graham 1d76d90bcb Update dependencies 2 years ago
Graham 5c77ee4bd2 Format CacheExporter 2 years ago
Graham 1a78ef3c7d Throw an exception if header is truncated 2 years ago
Graham 73eb30dbf9 Add game, environment, language, build and timestamp to file names 2 years ago
Graham b99ae4bb09 Update dependencies 2 years ago
Graham fb0d7ef923 Add link to the RuneScape Archive 2 years ago
Graham 799277b386 Simplify JagexGzipOutputStream 2 years ago
Graham 0d097f7d2c Update Gradle 2 years ago
Graham 57bdd6c0f4 Update dependencies 2 years ago
Graham ec4eb6464f Update dependencies 2 years ago
Graham 26d5d5fd6e Use JagexGzipOutputStream to compress gzipped JS5 containers 2 years ago
Graham 64b1e2e5b9 Add gzip-jagex command 2 years ago
Graham 71e7c6f8ea Remove createLaxInputStream() method 2 years ago
Graham 6c508922fc Add JagexGzipOutputStream 2 years ago
Graham a761a26cc8 Manage plugin versions in libs.versions.toml 2 years ago
Graham 0b2f13422b Update dependencies 2 years ago
Graham e195f5f37f Update dependencies 2 years ago
Graham 08080489b6 Replace BIPUSH 1 with ICONST_1 2 years ago
Graham cf983794fb Fix InvalidKeyTransformer bug and comment 2 years ago
Graham 7cde3a4552 Add link to the RuneScape Preservation Unit wiki page 2 years ago
Graham 64713fb862 Add --decode-js5response flag to import-master-index 2 years ago
Graham dbbc232e81 Update dependencies 2 years ago
Graham 71f775f282 Add JSON version of the /caches page 2 years ago
Graham 759d4bf1f1 Add XteaPluginTest run configuration 2 years ago
Graham 4aa75cf602 Update copyright year 2 years ago
Graham de07a41fde Add JUnit dependencies to Java modules 2 years ago
Graham 660c12676e Only remove handlers from pipeline if future is successful 2 years ago
Graham 207b99d937 Update dependencies 2 years ago
Graham b53149ce6b Add RuneLite plugin for collecting XTEA keys 2 years ago
Graham 346302fc05 Add API endpoint for receiving keys 2 years ago
Graham df55c3ece3 Fix reconnection on client-side timeouts in the JS5 client 2 years ago
Graham 3ce76abde7 Update dependencies 2 years ago
Graham 6bfdb07f42 Update comment to confirm the guess was correct 2 years ago
Graham 84a43e9fbe Update dependencies 2 years ago
Graham 8a3b1dff73 Fix off-by-one error in JS5 response buffer capacity calculation 2 years ago
Graham 469fe2eecc Add environment and language columns to the caches table 2 years ago
Graham bc51a68cb3 Update dependencies 2 years ago
Graham adc1138ca4 Convert packclass try/catch count to an unsigned short smart 2 years ago
Graham 211041ab6d Update support build list 2 years ago
Graham 3b4fcad8f4 Add link to RuneWiki 2 years ago
Graham 334424e57a Fix addMasterIndex() 2 years ago
Graham f4ae5540d7 Skip version list entries with a version of zero 2 years ago
Graham 0cf1881db4 Rename 'Indexes' column to 'Archives' 2 years ago
Graham ab964a2b1f Skip corrupt files when importing a legacy cache 2 years ago
Graham f079c415f5 Add initial support for archiving legacy caches 2 years ago
Graham ebfe01e4c4 Make executeOnce public 2 years ago
Graham 2eaba6a680 Fix cache size calculation 2 years ago
Graham 39d2ad7ee3 Fix ByteBuf leak 2 years ago
Graham 8448f74b3c Add ChecksumTable and VersionList classes 2 years ago
Graham 007f7d68ef Add legacy cache support to DiskStoreZipWriter 2 years ago
Graham cad6e10eee Update dependencies 2 years ago
Graham ab2c56f0af Set HttpClient timeouts 2 years ago
Graham be1f293b0a Use multi-line comment 2 years ago
Graham ac9dc83031 Rename legacyData to legacy and make it public 2 years ago
Graham 64aa0049ef Update Gradle 2 years ago
Graham a12aafe608 Update dependencies 2 years ago
Graham 046ccbc2c9 Optimize the master_index_stats view 2 years ago
Graham 26c6ad21e3 Add class for reading and writing .jag archives 2 years ago
Graham 3734d4709b Update dependencies 2 years ago
Graham 02f35fd014 Fix legacy DiskStore support 2 years ago
Graham b1c3cbc7e3 Format libs.versions.toml 2 years ago
Graham 407aff8dd8 Add support for .dat (instead of .dat2) file to DiskStore 2 years ago
Graham f38253f2fc Add else branch to non-exhaustive when statements 2 years ago
Graham 5cbaf55dd6 Add hash function used by pre-JS5 caches 2 years ago
Graham de1ea8bc6f Update dependencies 2 years ago
Graham 547cc95580 Refactor more code 2 years ago
Graham 5e82f5dcc0 Update dependencies 3 years ago
Graham 1efa370fdd Reconnect to the JS5 server if we lose connection while parsing indexes 3 years ago
Graham 20868a39ad Fix warnings in CacheExporter 3 years ago
Graham 09d3b6dbbf Update dependencies 3 years ago
Graham 2a6ffa87a6 Fix elvis operator on non-nullable type warning 3 years ago
Graham 3368818ca1 Download names from Polar's fork of RuneStar 3 years ago
Graham a5b35ae9c3 Update dependencies 3 years ago
Graham eac114884b Update dependencies 3 years ago
Graham 405603f2f4 Update dependencies 3 years ago
Graham f0cd4afcf0 Add sorting and filtering to the caches table 3 years ago
Graham 76ad27a380 Add junit-platform-launcher dependency 3 years ago
Graham 0ff6287122 Update dependencies 3 years ago
Graham e1a650aec4 Update introduction text 3 years ago
Graham b1590c2e98 Update dependencies 3 years ago
Graham da131000ba Create root directory in tar/zip files 3 years ago
Graham 3848a89f68 Ensure a directory is created for the ARCHIVESET archive 3 years ago
Graham 36f5efa1ad Serve flat file caches as .tar.gz files instead of .zip files 3 years ago
Graham 3508a01c02 Refactor wall decoration offsets 3 years ago
Graham a1f5ef8bff Add Rasteriser class 3 years ago
Graham 045e4e0fa6 Remove unused methods 3 years ago
Graham 9e220d800f Refactor several static methods 3 years ago
Graham c71b7f846f Refactor IntUtils.clamp 3 years ago
Graham 0e0f084d5e Refactor another sort method 3 years ago
Graham 1216a61928 Rename PlayerAppearance.DEFAULT 3 years ago
Graham 7a511bfa1e Refactor obj sprite rendering code 3 years ago
Graham d97d7f0b18 Update dependencies 3 years ago
Graham 9e66e0b552 Add sprite encoder/decoder 3 years ago
Graham aaea619550 Update dependencies 3 years ago
Graham cbb2a90388 Disable .dat2/.idx link if the cache is too big for a DiskStore 3 years ago
Graham a52955fe4b Switch to ktor's CIO backend 3 years ago
Graham 36e16a8687 Update Gradle 3 years ago
Graham 164e70f333 Update dependencies 3 years ago
Graham c4df2a4aae Update dependencies 3 years ago
Graham 0ec7736764 Log the number of XTEA keys imported 3 years ago
Graham b3d6b112d2 Add Logback configuration file 3 years ago
Graham 66c3027f8a Update dependencies 3 years ago
Graham e31fd48b0a Remove old idea-bug reference 3 years ago
Graham 7915698cda Reduce CacheExporter BATCH_SIZE 3 years ago
Graham 1f474be1bb Update dependencies 3 years ago
Graham d985fded7e Refactor some entity-related code 3 years ago
Graham 2e81cbce02 Refactor more audio-related code 3 years ago
Graham 28e7251a14 Rename Skybox to SkyBox for consistency 3 years ago
Graham 821cb732fd Remove signature verification from the cache downloader 3 years ago
Graham 12203912c1 Update dependencies 3 years ago
Graham c79ce805df Add implementation of the client's song format 3 years ago
Graham 739d5faea7 Refactor some interface-related code 3 years ago
Graham d32e690d5b Update dependencies 3 years ago
Graham bdc9fa1f61 Fix use of deprecated classes/properties in build.gradle.kts 3 years ago
Graham e309add443 Refactor more MiniMenu code 3 years ago
Graham dc2f39ed0d Refactor remaining parts of Cs1ScriptRunner 3 years ago
Graham 8240b48089 Refactor texture-related code 3 years ago
Graham 6634f3e711 Update dependencies 3 years ago
Graham dbff2e10ad Refactor some animation-related code 3 years ago
Graham 47642e9b42 Improve logging in ResourceTransformer 3 years ago
Graham b2f9bd6ded Update client checksum in the giant try/catch block in the loader 3 years ago
Graham 0781e23874 Add update links to each master index 3 years ago
Graham f2dc8beeec Update dependencies 3 years ago
Graham 18012a5f38 Update dependencies 3 years ago
Graham 6f02ab2f65 Add NXT cache downloader 3 years ago
Graham 0e1046d457 Fix {read,write}LineNumbers 3 years ago
Graham 2b3e9d318a Add commands for packing and unpacking pack200/packclass files 3 years ago
Graham cf6e7cf8e7 Add methods for reading RSA keys from memory 3 years ago
Graham 1155eb7eb8 Set writerIndex correctly in ByteBufBodyHandler 3 years ago
Graham e4b617d712 Fix multiple @Inject constructors in ByteBufBodyHandler 3 years ago
Graham 2fc7b0847a Make CONTRIBUTING.md slightly more accurate 3 years ago
Graham 5a8a571b28 Update Gradle 3 years ago
Graham 47782ecdd7 Update dependencies 3 years ago
Graham 9b9eb363c8 Refactor AttachLocRequest and some animation-related code 3 years ago
Graham 2cc4b34ab3 Replace MutableList with List in ByteBufBodyHandler 3 years ago
Graham e3fde7f2a0 Update dependencies 3 years ago
Graham a4e74a6752 Fix ConstantPool string limit 3 years ago
Graham 1101f6e885 Add ByteBufBodyHandler 3 years ago
Graham 21ed41c307 Use applet viewer config to find the hostname of a JS5 server 3 years ago
Graham 11a43242b9 Add applet viewer config parser 3 years ago
Graham 637e311a67 Fix SELECT DISTINCT query 3 years ago
Graham fc6e739879 Add e prefix to the name generator 3 years ago
Graham 794463febd Append the version trailer to the correct buffer 3 years ago
Graham d925b68db3 Remove duplicate keys from valid.json 3 years ago
Graham 2e5fbe7ed7 Update Gradle 3 years ago
Graham 689b1493de Update dependencies 3 years ago
Graham 1585090c68 Rename TextureMeta to Material 3 years ago
Graham c2428494b6 Rename Material to MaterialRenderer 3 years ago
Graham 85f497f619 Treat a world list checksum of 0 as 1 3 years ago
Graham 3edc5f1907 Add InitGameConnection codec 3 years ago
Graham 8ff91b5ee2 Implement world list packet 3 years ago
Graham 1b83f4b4b3 Add CacheProvider 3 years ago
Graham 6a0266e88f Add methods for looking up entries in an enum 3 years ago
Graham 6194435313 Disable io_uring for now 3 years ago
Graham f6c28e2b40 Update Kotlinter 3 years ago
Graham 6c6e23b1e2 Update dependencies 3 years ago
Graham 9c545ada73 Reduce table width 3 years ago
Graham c870ae53dc Fix country ID in the documentation 3 years ago
Graham e52c20ee32 Fix name of world offest variable 3 years ago
Graham 609b3236d7 Add space between each config item 3 years ago
Graham ea6d377628 Update dependencies 3 years ago
Graham 3829f228ef Add world list packet structure 3 years ago
Graham ac5743a86a Update dependencies 3 years ago
Graham 3eafe45f78 Use 2 space indents in JSON files 3 years ago
Graham 3889c8a034 Terminate all threads if the server fails to bind to a port 3 years ago
Graham 9b0260a3df Add GameService 3 years ago
Graham 60ecda74ec Add InvType 3 years ago
Graham 62abcbad8c Add DI support to the *TypeList classes 3 years ago
Graham 2c43930db2 Move Js5Archive and Js5ConfigGroup to cache-550 3 years ago
Graham 849b083400 Add {Enum,Param,Struct,Varbit,Varp}Type implementations for 550 3 years ago
Graham 21cf2526c5 Format Http.kt 3 years ago
Graham e5fc516ef1 Add base classes for encoding/decoding configs 3 years ago
Graham e0ec3781ae Refactor the Texture class 3 years ago
Graham bdbd8617b9 Update dependencies 3 years ago
Graham 50c1dd9ce8 Add cache/XTEA key layout to the README 3 years ago
Graham fe3bfb4b12 Remove border/level parameters from the CheckedInt32 group 3 years ago
Graham 95cb97bae8 Add capacity properties/methods to Archive and Cache 3 years ago
Graham 65194fae43 Rename poll to removeFirstOrNull 3 years ago
Graham 7d04774d60 Fix removal of unused methods 3 years ago
Graham 2b9f423e9a Remove unused Iterable extensions 3 years ago
Graham 774744579d Replace LinkedHashSet queues with UniqueQueue 3 years ago
Graham 49e5781288 Add addAll and clear methods to UniqueQueue 3 years ago
Graham 82c78d514d Update dependencies 3 years ago
Graham 198acbdf7a Rename username variable in Base37 class 3 years ago
Graham 493886a8bd Improve error messages in CrossDomainChannelHandler 3 years ago
Graham e0aefe506d Improve name of flag in js5NetworkLoop 3 years ago
Graham 55d72dacbe Pass StreamCipher to PacketCodecs 3 years ago
Graham a50e0ea998 Restrict supported HTTP methods 3 years ago
Graham a8d048de00 Remove pipelining support from the crossdomain handler 3 years ago
Graham acebd05c04 Add comment explaining why we sort the constant pool entries 3 years ago
Graham ebbc81740d Check the ConstantPool isn't too large 3 years ago
Graham d825729a2c Add string index constants 3 years ago
Graham f9007688a1 Fix SASTORE comment 3 years ago
Graham 8cd7645536 Add sanity check to ConstantPool builder 3 years ago
Graham 86a84c6986 Serve .jar files with the correct MIME type 3 years ago
Graham 89f7a192f9 Optimise inbound message matcher creation 3 years ago
Graham 29693a39cf Add timeout support for all services on the RS2 port 3 years ago
Graham a43e98e8f4 Add HTTP keep-alive support 3 years ago
Graham 03fc499566 Improve robustness of reference counting in JaggrabChannelHandler 3 years ago
Graham 620808cb97 Improve the HTTP implementation 3 years ago
Graham 92a01b6262 Add cross-domain policy support 3 years ago
Graham 2a6b97480c Add implementation of Jagex's custom .class file format 3 years ago
Graham ad0cdb6056 Add initial high-level cache API 3 years ago
Graham 6f13a0a737 Add readVarInt and writeVarInt extension methods 3 years ago
Graham 29fecec34e Add support for overriding the charset in readString/writeString 3 years ago
Graham f26e6ef0b9 Update Kotlin 3 years ago
Graham cdc1edbb1d Use more realistic test data in the JAGGRAB test 3 years ago
Graham 440711def0 Fix JAGGRAB request parsing 3 years ago
Graham 7eae597892 Move RsaKeyProvider to the config module 3 years ago
Graham 7e8883b430 Move Store/Js5MasterIndex providers to the game module 3 years ago
Graham 7692862c7c Add RequestWorldList packet 3 years ago
Graham 235ee41e32 Update dependencies 3 years ago
Graham ea725b3881 Add extension methods for reading/writing null-terminated Cp1252 strings 3 years ago
Graham bdbfb61590 Ensure master index format is set to VERSIONED 3 years ago
Graham 55016d7223 Rename updated to version 3 years ago
Graham 01b50e33f7 Fix resolve_group() if the version number is greater than 65535 3 years ago
Graham bc907d5309 Add support for reading existing .dat2m caches 3 years ago
Graham 6be16f1748 Update dependencies 3 years ago
Graham 080677b45c Name the shutdown hook thread 3 years ago
Graham 43b8be3ff3 Add shutdown hook 3 years ago
Graham dc4b769f29 Add startup log messages 3 years ago
Graham fbb9694feb Close JS5 connection if an invalid file is requested 3 years ago
Graham f48d23c4b9 Update Gradle 3 years ago
Graham 20f1b86ce9 Update dependencies 3 years ago
Graham bc018a3b0f Add initial JAGGRAB, JS5 and HTTP servers 3 years ago
Graham 65b3e1315a Add Store and Js5MasterIndex providers 3 years ago
Graham a3cca0e6c3 Add Protocol constants 3 years ago
Graham 484c52af8e Add InitJaggrabConnection packet 3 years ago
Graham 4d5c9153ab Add support for converting a Netty Future to a CompletableFuture 3 years ago
Graham d3b89d5b41 Remove AUTO_READ option from the ServerChannel config 3 years ago
Graham 81e665e9d8 Add UniqueQueue 3 years ago
Graham ce741279b4 Add read-only Js5Index.{Group,File} types 3 years ago
Graham e0d29e5ac2 Update dependencies 3 years ago
Graham 1c0bf7529c Update Bootstrap 3 years ago
Graham ee151aef56 Add constants to BitBuf 3 years ago
Graham ff19c57396 Rename Js5File to Js5Pack 3 years ago
Graham bd5a925f9b Add BitBuf 3 years ago
Graham 1954f408e7 Replace deprecated capitalize() function 3 years ago
Graham b37e4d0d0b Use alternatives for most methods deprecated in Kotlin 1.5 3 years ago
Graham 7f58b50a47 Update to Kotlin 1.5.0 3 years ago
Graham 40eb0302f7 Downgrade to working Kotlinter 3 years ago
Graham d7e9f83953 Return ByteBuf from extension methods for chaining 3 years ago
Graham 9bb511369d Add automatically-generated ByteBuf extensions 3 years ago
Graham fae51b8eb5 Update dependencies 3 years ago
Graham c6f18ceca9 Exclude Flyway from minimization 3 years ago
Graham 852cd7d1d2 Update dependencies 3 years ago
Graham cf8110e5a7 Fix possible off-by-one error in KeyBruteForcer 3 years ago
Graham e9f040df79 Update dependencies 3 years ago
Graham db43c73085 Add lax gzip implementation 3 years ago
Graham 689fe7c372 Refactor more code 3 years ago
Graham 33085cb965 Update dependencies 3 years ago
Graham 341fad9b83 Improve consistency of MIDI event/message naming 3 years ago
Graham a5db11ae1f Refactor more audio-related code 3 years ago
Graham 9a9fa2bc09 Rename Class6 to Material7 3 years ago
Graham 1726aaecd2 Refactor readZoneLocs 3 years ago
Graham 629a78795b Format libs.versions.toml 3 years ago
Graham 9e8d1fca1c Use type-safe project accessors 3 years ago
Graham 213808f609 Add pluginManagement block 3 years ago
Graham 3a4fdc7ee0 Replace JCenter with the Gradle Plugin Portal 3 years ago
Graham 6498d39306 Add Gradle 7 version catalog 3 years ago
Graham 1a2f4bd520 Update Gradle 3 years ago
Graham 7c01a19c58 Update dependencies 3 years ago
Graham b0f1632105 Improve Js5MasterIndex code coverage 3 years ago
Graham eca3a90c78 Improve Js5Compression code coverage 3 years ago
Graham 3293454c17 Add support for importing keys in binary format 3 years ago
Graham 09961589e3 Fix map square coordinate text 3 years ago
Graham 7d041e6e22 Replace readAllBytes() with readBytes() 3 years ago
Graham ba60fecb50 Add support for downloading caches in flat file format 3 years ago
Graham 6aea0cfc8e Add FlatFileStoreZipWriter 3 years ago
Graham e0a2df889b Add more information to the individual cache pages 3 years ago
Graham d89c45940b Infer the number of bytes to skip automatically in the material decoder 3 years ago
Graham 1f9e7798e7 Improve size estimation footnote 3 years ago
Graham 193af138fe Use non-breaking space in the size column 3 years ago
Graham 9be1a54b76 Add estimated cache size column 3 years ago
Graham ee567cc76f Treat empty loc groups as valid 3 years ago
Graham 40d6ccbb61 Add ent's output to the keys page 3 years ago
Graham 221252895c Check exit code of ent 3 years ago
Graham 09b6605a99 Update dependencies 3 years ago
Graham 4e713b53f8 Make T3 errors non-fatal 3 years ago
Graham 593f46d9fd Add percentages to the keys page 3 years ago
Graham 6c728d758c Add total number of encrypted/validated groups to the keys page 3 years ago
Graham 6cf9eb5043 Set source name for original caches to 'Jagex' 3 years ago
Graham f1c9fed36f Rename 'Name' column to 'Source(s)' 3 years ago
Graham 879e0f5e13 Refactor underlay/overlay-related code 3 years ago
Graham ab58a83a5d Update dependencies 3 years ago
Graham 4337020b6b Add endpoint for exporting all keys in bulk 3 years ago
Graham 93ee863e20 Add initial support for plotting a map of valid/invalid XTEA keys 3 years ago
Graham dbd169aa1a Make fastutil part of the cache library's API 3 years ago
Graham da3cc57b3f Add link to download keys in the text format 3 years ago
Graham f6961a50f1 Add cache size in bytes to master_index_stats 3 years ago
Graham 8bc25e7e47 Set compression lewel to -9 3 years ago
Graham aad469c05d Generate XTEA key zip reproducibly 3 years ago
Graham c4f807d38f Add X-Forwarded-For support 3 years ago
Graham 2d136c89e9 Add zipped text version of the XTEA key endpoint 3 years ago
Graham a02eb169f4 Fix NPE in TextKeyReader if the input is invalid 3 years ago
Graham a6fa7bf5b6 Throw exception before importing a cache if its master index is empty 3 years ago
Graham ae88bca924 Add ent command for analysing the randomness of keys 3 years ago
Graham e4776f7c3c Only call games.single() when game is accessed 3 years ago
Graham f870410a09 Update dependencies 3 years ago
Graham e6af0affb6 Ignore corrupt indexes in Js5MasterIndex::create 3 years ago
Graham bf86fa48e9 Update dependencies 3 years ago
Graham 8c395bcf4b Add index on key_id 3 years ago
Graham a8fbfd851c Replace PL/pgSQL functions with inlineable SQL functions 3 years ago
Graham 5d1fa42623 Remove pointless LATERAL JOINs 3 years ago
Graham b792b65f5e Remove smarter resolution algorithm for now 3 years ago
Graham 3a897d4e67 Downgrade Netty 3 years ago
Graham 18d7fdc343 Resolve collisions by prioritising groups from the same source 3 years ago
Graham 1f735f4b99 Remove duplicate descriptions 3 years ago
Graham f04d163d98 Add non-truncated version numbers to the collisions view 3 years ago
Graham f59d9213eb Update dependencies 3 years ago
Graham c49ce684c8 Optimize master_index_stats 3 years ago
Graham 24d5b91ebe Mark functions as stable, strict and parallel safe 3 years ago
Graham cba34b54c9 Add resolve_{group,index} functions 3 years ago
Graham a8794aca99 Include empty master index in master_index_stats 3 years ago
Graham b685d901aa Mark empty master index as invalid 3 years ago
Graham 38be1905cb Fix cache source URL list 3 years ago
Graham 006c8f7b9b Improve grammar 3 years ago
Graham eeb82d2e47 Fix typo in master_index_stats JOIN 3 years ago
Graham 9bce5cc1b3 Replace LEFT JOIN on sources/games with a JOIN 3 years ago
Graham 4ad9b31aae Update dependencies 3 years ago
Graham c0f9590f48 Add foreign key to source_groups 3 years ago
Graham 4353d8a616 Refactor identikit-related code 3 years ago
Graham 69a511a3c5 Update dependencies 3 years ago
Graham b98d045cfe Add group source tracking 3 years ago
Graham 4856f79152 Update dependencies 3 years ago
Graham 853f2cff91 Add assignment operator support to HexLiteralTransformer 3 years ago
Graham 99b725dc8f Rename clearBuffer to fillBuffer 3 years ago
Graham 538f4d06c6 Refactor (mostly) font-related code 3 years ago
Graham 4deabceb9e Move container_id to the end of the groups index 3 years ago
Graham d8fd55f5cc Update dependencies 3 years ago
Graham 0007a2917b Update dependencies 3 years ago
Graham bb293d3e3f Add missing ORDER BY to queries fetching the next container/key 3 years ago
Graham 995cdbd1b7 Add index to speed up the XTEA key brute force command 3 years ago
Graham ea63769fe9 Add container IDs to the collisions view for debugging 3 years ago
Graham 76dba3f404 Count indexes with a zero version/checksum as valid 3 years ago
Graham 2411a16bc2 Always show vertical scroll bar to improve horizontal alignment 3 years ago
Graham 5f5e7e9c1e Add archive home page 3 years ago
Graham 6ac2a31e36 Add link to the /pub folder 3 years ago
Graham b1ccd9371d Defer container UPDATEs until we've finished the current statement 3 years ago
Graham 42c3c4c120 Fix over-counting in the master_index_stats view 3 years ago
Graham 94992601f6 Update dependencies 3 years ago
Graham d665749481 Use a shorter name for caches downloaded directly from a JS5 server 3 years ago
Graham 5d7bf1d59b Don't merge names/descriptions if they are equal to the existing ones 3 years ago
Graham 509d88b18f Add support for storing multiple build numbers per master index 3 years ago
Graham 9a672153f9 Change the overwrite flag to only overwrite the name/description 3 years ago
Graham 55870e428e Add navbar-expand class to the nav element 3 years ago
Graham f6a78247e1 Update dependencies 3 years ago
Graham 715e8ec47b Refactor integer power method 3 years ago
Graham 25ee52738d Use minimum build number when merging master indexes 3 years ago
Graham c06417bed7 Add PRIMARY KEY to tmp_keys for sanity checking 3 years ago
Graham 3d09c4d90a Make XteaKey a data class 3 years ago
Graham df2aab7639 Fix position of 'Calculating...' text 3 years ago
Graham 8a25d3d31a Use CREATE EXTENSION IF NOT EXISTS in V1__init.sql 3 years ago
Graham d0df60cc1c Add archiving service config file 3 years ago
Graham 7fdbe5113f Add address and port options to WebCommand 3 years ago
Graham 23bb1ef13c Add refresh-views command 3 years ago
Graham 604ba00154 Update Gradle 3 years ago
Graham a977f79409 Update dependencies 3 years ago
Graham 70644b5e06 Combine master_index_{archive,group}_stats 3 years ago
Graham 20988a70cc Add size column to the master_index_group_stats view 3 years ago
Graham f20070d8c3 Fix indentation in V1__init.sql 3 years ago
Graham f8c44f1de4 Rename master_index_valid_{indexes,groups} to resolved_{indexes,groups} 3 years ago
Graham 29541b03a8 Remove collision TODO from V1__init.sql 3 years ago
Graham 4f0a37e13e Format caches/index.html 3 years ago
Graham 3a067b8b9c Add support for downloading keys from Polar's archive 3 years ago
Graham b2ccbad031 Add contentType and charset extension properties to HttpResponse 3 years ago
Graham 326371312d Use Set instead of List internally in JsonKeyReader 3 years ago
Graham 03cb7701c5 Move migrations to a separate directory to speed up classpath scanning 3 years ago
Graham cd874f4b2c Add connection pooling to the archiving service 3 years ago
Graham c5bd044574 Add CloseableInjector 3 years ago
Graham a0f7bf3922 Remove duplicate json dependency 3 years ago
Graham e0d9f2f0a2 Fix module descriptions (Guava module -> Guice module) 3 years ago
Graham 833373a70e Fix LEFT JOINs in queries for listing missing groups 3 years ago
Graham d7919da2a3 Improve error messages in Js5ChannelHandler 3 years ago
Graham 59d75721d1 Add JS5 client timeout 3 years ago
Graham e3e0094b43 Remove empty loc group special case 3 years ago
Graham aa7b59e4b9 Add a page for each cache 3 years ago
Graham 47b1bd5bf0 Replace concatenation with classappend 3 years ago
Graham d5b1bf016e Remove redundant active class 3 years ago
Graham 9638b0311a Add explicit line breaks and non-breaking spaces to improve appearance 3 years ago
Graham 31689bb7d3 Update dependencies 3 years ago
Graham 37d86af7c1 Add command for downloading group names from RuneStar 3 years ago
Graham 3f8b8eec1a Add view for detecting collisions 3 years ago
Graham 5f5d1aad93 Fix divide by zero in CacheExporter 3 years ago
Graham 5e81ccc92c Improve footnote style 3 years ago
Graham a920570f04 Fix total archive, group and key counts 3 years ago
Graham 1cbd97d3c9 Fix case (on -> ON) 3 years ago
Graham 1c301f6aa5 Improve footnote wording 3 years ago
Graham 0e80066c29 Format caches/index.html 3 years ago
Graham c260548d73 Add command for downloading keys from RuneLite and OpenOSRS 3 years ago
Graham 5da59135ea Add HTTP module 3 years ago
Graham 86e9963a75 Format timestamps more concisely 3 years ago
Graham b5a4f75fb9 Group cache and keys links 3 years ago
Graham 0775449eae Make the caches table responsive 3 years ago
Graham e56d16ab91 Right align numeric columns 3 years ago
Graham 4b4b6fabc0 Add percentage of valid indexes, groups and keys to the table 3 years ago
Graham 8eb1f82048 Log a warning in CacheImporter if a group is corrupt 3 years ago
Graham e1f1da1cc0 Check that there is no trailing data after a group in CacheImporter 3 years ago
Graham b067020cad Ignore trailing blocks with a non-zero next block pointer 3 years ago
Graham 75d4446b92 Ensure uncompressIfKeyValid always consumes the entire container 3 years ago
Graham fffc96a83f Add navbar to the archive web interface 3 years ago
Graham 416dabec4c Include empty loc groups in the total number of known keys 3 years ago
Graham 2ac2ab8230 Add groups and total_uncompressed_length to master_index_archives 3 years ago
Graham 86f100ff4f Format CacheExporter 3 years ago
Graham 330ef655ba Remove PACKAGES_IMPORT_LAYOUT 3 years ago
Graham b94cc80785 Update HTML code style 3 years ago
Graham bce6270957 Set background colour of the indexes, groups and keys cells 3 years ago
Graham 3ff6cd2d02 Add custom JSON pretty printer 3 years ago
Graham bdab767ca7 Add thousands separators to group/key counts 3 years ago
Graham 554520b285 Add initial Bootstrap style 3 years ago
Graham 81e2dedee5 Add number of valid indexes, groups and keys to the web interface 3 years ago
Graham 971fb642c1 Remove pointless cast 3 years ago
Graham afcd1fac36 Add workaround for IDEA-256707 3 years ago
Graham bdaf5aae2c Rename WHIRLPOOL to DIGESTS for consistency with the Js5Index flags 3 years ago
Graham 53db5b3314 Add support for the current master index format 3 years ago
Graham 192b24b9bd Improve JsonKeyReader 3 years ago
Graham 31db959a46 Add XteaKeyDeserializer 3 years ago
Graham 704687e262 Remove /keys/submit endpoint 3 years ago
Graham e1f33158f2 Throw an exception if the JS5 connection closes unexpectedly 3 years ago
Graham 2bce00be54 Remove pointless return 3 years ago
Graham 4ca9bcb6d2 Rename master_index_id to last_master_index_id 3 years ago
Graham e565b0cb4f Tighten JOIN conditions in importMasterIndexAndGetIndexes 3 years ago
Graham e3865180ca Remove tmp_groups table 3 years ago
Graham 692ca20344 Add processGroup method to Js5ChanneHandler 3 years ago
Graham 06ff7a9d3b Download all groups that changed between the previous and current cache 3 years ago
Graham 48ce47ad7b Remove tmp_indexes table 3 years ago
Graham 2aab020e67 Use int for master index ids consistently 3 years ago
Graham 5d8f89e319 Add master_index_valid_{indexes,groups} views to reduce duplication 3 years ago
Graham 60812f22eb Update dependencies 3 years ago
Graham 5cbb87e788 Update copyright year 3 years ago
Graham e8fd432f14 Combine key validation with uncompression 3 years ago
Graham 359891c01e Convert all test objects to classes 3 years ago
Graham 91d4e46c89 Rename GroupKey to Key 3 years ago
Graham 9b409c8331 Improve JUnit dependencies 3 years ago
Graham c295ecb0a0 Allow nullable ReferenceCounted objects to be passed to use 3 years ago
Graham 3289af5ddf Add uncompressed_{length,crc32} columns to the containers table 3 years ago
Graham 177376f47e Update dependencies 3 years ago
Graham b003e02ef4 De-duplicate key validation code in KeyBruteForcer 3 years ago
Graham 5e6afb88f6 Improve RSA method naming 3 years ago
Graham 2c7cb5e054 Minimise code in the NotImplementedError catch block 3 years ago
Graham 0016b8ab7d Replace AssertionError with IllegalStateException 3 years ago
Graham 2ff0ccc137 Update dependencies 3 years ago
Graham 8b1880cf09 Add heading to download links column 3 years ago
Graham 12e3a0eed2 Remove extraneous newline 3 years ago
Graham eae9a3e2f4 Remove master index digest column from the web page 3 years ago
Graham 98d25539b9 Combine RSA encrypt and decrypt methods into a single crypt method 3 years ago
Graham 619424321f Add index version consistency check 3 years ago
Graham 97640774f9 Store index protocol, version and flags in the database 3 years ago
Graham 1c061c0f66 Store master index format in the database 3 years ago
Graham 5d7bd5b5c7 Add support for signed master indexes 3 years ago
Graham ed7eb10411 Relax isPrivate checks in the encrypt/decrypt methods 3 years ago
Graham e9e7b7848d Add constant for the RSA magic byte 3 years ago
Graham 7b6c0cf1fd Use block comments for multi-line comments 3 years ago
Graham 4ccebc7ef8 Replace assertThrows with assertFailsWith 3 years ago
Graham 8fcf7cae46 Test the master index length validation code 3 years ago
Graham 4126abfaad Add support for unversioned master indexes 3 years ago
Graham 4154e4fdb4 Store non-truncated versions in the archiving service if available 3 years ago
Graham cbeb9a3a67 Add mapsquare key for compatibility with Polar's XTEA key files 3 years ago
Graham 5cfb234cc7 Use Ktor's Content-Disposition builder 3 years ago
Graham 412d6f4c1f Add XTEA key export endpoint 3 years ago
Graham 936968363b Add CacheModule 3 years ago
Graham 46b311c5ad Add XteaKey Jackson serializer 3 years ago
Graham 98dcbedeaf Sort install() calls in ArchiveModule 3 years ago
Graham a5ac7b0b28 Fix heading case 3 years ago
Graham 2577eeb6a4 Update Gradle 3 years ago
Graham 11bd7e4fc7 Update dependencies 3 years ago
Graham fd914ba3d3 Add note about the AArch64 patch to the JAGGRAB documentation 3 years ago
Graham 4fa67c37fc Improve code for merging an old and new master index together 3 years ago
Graham b410b69771 Update dependencies 3 years ago
Graham a19a69b21e Add name and description to the master_indexes table 3 years ago
Graham dc9852e77c Indent all tags in the HTML code style 3 years ago
Graham 47127113f4 Add initial archiving service web interface 3 years ago
Graham 6e9ba80d0c Rename dev.openrs2.net to org.openrs2.net 3 years ago
Graham a3593e9326 Rename master_index_entries to master_index_archives 3 years ago
Graham 0b00c8452a Update loader.yaml to account for the removed @Pc annotation 3 years ago
Graham 16523ec3de Update dependencies 3 years ago
Graham 4ba78e26b9 Create .idx files for empty indexes 3 years ago
Graham 273f7eddf8 Use a CTE to reduce duplication in CacheExporter 3 years ago
Graham 9834dccfdd Add indexes to the groups table 3 years ago
Graham 723bd42a8e Simplify encrypted flag in the Container class 3 years ago
Graham 885fa3d0ed Add AArch64 macOS support 3 years ago
Graham 3091409f82 Add arch/OS enums to avoid hard-coding the platform detection patch 3 years ago
Graham 80957aeca0 Add InsnList.clone() extension function 3 years ago
Graham d5d76f9301 Add DiskStoreZipWriter 3 years ago
Graham b61992a20f Replace Paths.get() with Path.of() 3 years ago
Graham 64e85a43de Update dependencies 3 years ago
Graham f11bf003f7 Refactor sine in VorbisSound 3 years ago
Graham ad03b67b5f Refactor the Filter class 3 years ago
Graham b5516982a3 Update dependencies 3 years ago
Graham b8aada26f3 Refactor texture op 29 sub-ops 3 years ago
Graham 2185eedbb6 Rename some MapElement-related code 3 years ago
Graham 002200101d Partially refactor the Skybox class 3 years ago
Graham e4c814b86b Refactor some model- and animation-related code 3 years ago
Graham 9c3959769f Add Js5Index and Js5MasterIndex to the glossary 3 years ago
Graham 1dc2918956 Change 'area' to 'build area' 3 years ago
Graham 832d6ecb6f Improve coordinate system formatting 3 years ago
Graham 451d59b9e6 Add missing retain() call to Group.pack() 3 years ago
Graham f1c3c65811 Use upper case hex digits in golden ratio constant 3 years ago
Graham 1e3257acf7 Use consistent case for Unicode escapes in the Whirlpool S-box 3 years ago
Graham f8835b7581 Always return a mutable Int2ObjectSortedMap from Group.unpack() 3 years ago
Graham b3b46bee1a Add coordinate system documentation 3 years ago
Graham f099972bee Make the nioBufferCount() > 1 case in crc32 more concise 3 years ago
Graham 8db7b7cfbe Update Gradle 3 years ago
Graham 4ce8191c71 Update dependencies 3 years ago
Graham 6a87159f30 Add lengths and uncompressed checksum to index_groups 3 years ago
Graham f90b9df505 Replace Paths.get() with Path.of() 3 years ago
Graham 986d23e876 Update dependencies 3 years ago
Graham 7691f63d29 Make Js5Response extend DefaultByteBufHolder 3 years ago
Graham b6e91b756e Add ProjAnim to the glossary 3 years ago
Graham e4dc5ca34d Add ScriptVarType to glossary 3 years ago
Graham 411ee9f446 Refactor CS2-related code 3 years ago
Graham 010d5145bd Refactor Cs1ScriptRunner 3 years ago
Graham 8c0a3d8bc8 Refactor chat- and command-related code 3 years ago
Graham f23545aef8 Refactor more code 3 years ago
Graham a8a48b3574 Move repositories to new dependencyResolutionManagement block 3 years ago
Graham f9996ef0b0 Update glossary 3 years ago
Graham 77e227c93d Store game, build and timestamp in the master index table 3 years ago
Graham ab006bbf44 Reintroduce Clitk extensions module 3 years ago
Graham f3ea8e71fe Rename version to build 3 years ago
Graham 689c315bf3 Track current build number automatically 3 years ago
Graham fdd0f118f2 Add logging to Js5ChannelHandler 3 years ago
Graham d67a5e690f Add request() method 3 years ago
Graham 80a8889106 Call read() if there is any existing in-flight request 3 years ago
Graham d421d5150c Update dependencies 3 years ago
Graham 4743f38dbe Update dependencies 3 years ago
Graham f2f3da3281 Simplify testCrc32 slightly 3 years ago
Graham c70cfcfb30 Add ByteBuf.whirlpool() extension method 3 years ago
Graham 0981303391 Replace if/else sequence with when 3 years ago
Graham 6f815b6334 Move return outside try block 3 years ago
Graham 5ee2eccb83 Remove redundant init block 3 years ago
Graham 918f8ab379 Remove redundant visibility modifier 3 years ago
Graham d0dbf4c58e Make set subject of when 3 years ago
Graham ab3300a8c7 Return old NamedEntry after removal from NamedEntryCollection 3 years ago
Graham ed0b1db81b Flesh out the JS5 protocol introduction 3 years ago
Graham f74d8f8b45 Update Gradle 3 years ago
Graham 602328c37d Update dependencies 3 years ago
Graham af0a1e7457 Fix DiskStoreTest and FlatFileStoreTest on Windows 3 years ago
Graham 43cf436c22 Set Java compiler encoding to UTF-8 3 years ago
Graham cec6fce341 Refactor various bits and pieces 3 years ago
Graham 4e48ea0f41 Refactor the SoundBank class 3 years ago
Graham 5ea7f1ab6b Refactor the Vorbis implementation 3 years ago
Graham 186c5930a5 Add Js5Archive and Js5ConfigGroup constants for 550 3 years ago
Graham e384d634cc Reduce allocations if a singleton set is already mutable 3 years ago
Graham df68f6d801 Add Js5ConfigGroup object 3 years ago
Graham e18d751125 Add combined command for running both deobfuscators and the decompiler 3 years ago
Graham 70b35216c9 Flesh out JS5 protocol documentation 3 years ago
Graham 07603d3cfa Avoid copying the byte array in crc32() if possible 3 years ago
Graham 435a8b4eff Validate checksums in the JS5 client 3 years ago
Graham 4b1d25393c Add JS5 protocol documentation 3 years ago
Graham d837e72aee Enforce consistent version of Kotlin's stdlib across the whole project 3 years ago
Graham 0f939935cd Skip flush/read calls if no writes are made 3 years ago
Graham e0a9bc3ca6 Rename suspend to awaitSuspend 3 years ago
Graham 29ac56fe1e Replace flushRequests() with channelReadComplete() 3 years ago
Graham 6023569ce0 Add code for packing and unpacking groups 3 years ago
Graham f87d89fe7c Add initial command for downloading the cache from a JS5 server 3 years ago
Graham 72e9107900 Move original client back to nonfree/lib 3 years ago
Graham 36e4fa474b Fix comment 3 years ago
Graham 95108126f1 Skip addMasterIndex/addIndex if the index already exists 3 years ago
Graham fff63285fe Re-combine CacheImporter and ContainerImporter 3 years ago
Graham 4f16713f01 Update dependencies 3 years ago
Graham 4e90bd76b5 Remove the cache and cache_index tables 3 years ago
Graham 9e983135e2 Make InitJs5RemoteConnection a data class 3 years ago
Graham fa20f1e118 Fix Js5MasterIndex::create 3 years ago
Graham 3cb7ee13f9 Rename deob to deob-bytecode 3 years ago
Graham ded1d99924 Move original client code to nonfree/share/client 3 years ago
Graham c24d9ceb35 Rename bundler to patcher 3 years ago
Graham 30828308b2 Replace assertThrows with assertFailsWith 3 years ago
Graham 410bf909ff Ignore JUnit APIs with Kotlin equivalents 3 years ago
Graham 32fe61bd7d Replace org.junit.jupiter.api.Test with kotlin.test.Test 3 years ago
Graham ef152afab4 Re-wrap markdown files 3 years ago
Graham 2549c84028 Wrap .md files at 80 characters 3 years ago
Graham 9ddce82df4 Format code 3 years ago
Graham 4aa75c8fee Improve Js5MasterIndex test coverage 3 years ago
Graham f1433ec97f Add method for creating a Js5MasterIndex 3 years ago
Graham 03b30145a9 Replace INT with INTEGER for consistency 3 years ago
Graham 6ab47a9ba7 Change english to en in the client run configuration 3 years ago
Graham 452fabc408 Move semantic versioning text to CONTRIBUTING.md 3 years ago
Graham 19ddc608aa Update dependencies 3 years ago
Graham 7b4f2903fd Add initial login codecs required to negotiate a JS5 connection 3 years ago
Graham 7b9964461f Fix off-by-one erorr in the JS5 codecs 3 years ago
Graham 6a03ef1746 Replace xor with or in Js5ResponseEncoder 3 years ago
Graham 43672b0558 Add smart to the glossary 3 years ago
Graham 5ae0e86654 Add alt3 to the glossary 3 years ago
Graham 96ea95394c Add JAGGRAB request codec 3 years ago
Graham 30b605d719 Store master indexes in the database 3 years ago
Graham 19b6893681 Fix V1__init.sql package 3 years ago
Graham 42fec8e485 Rename getResource in the loader 3 years ago
Graham a8c1ecb429 Remove use of deprecated mainClassName property where possible 3 years ago
Graham c7a7da3af3 Add hop time formula to login.md 3 years ago
Graham 95d6583dfe Add initial implementation of the RS framing layer 3 years ago
Graham 61d525c542 Convert Js5MasterIndex.Entry to a data class 3 years ago
Graham 59b307360b Add protocol overview 3 years ago
Graham 36d3941bfe Document that multiple login packets can be sent in a single session 3 years ago
Graham 4bec1aac1c Fix spacing in login.md 3 years ago
Graham 70cc0d1d9a Add JAGGRAB documentation 3 years ago
Graham c9a87e7772 Add initial glossary 3 years ago
Graham b5b943f7b6 Add documentation summarising the cryptography used by the client 3 years ago
Graham bb18fc7d50 Add initial login protocol documentation 3 years ago
Graham 62271878b6 Replace check with require in Js5MasterIndex::read 3 years ago
Graham b8b0e2b361 Update dependencies 3 years ago
Graham bd6cdb5530 Convert sharable ChannelHandlers to objects where possible 3 years ago
Graham 1c13fec91f Replace `?: throw AssertionError()` with `!!` 3 years ago
Graham d78c382ea7 Add in operator support to NamedEntryCollection 3 years ago
Graham 1f93b272cd Add note about IDEA bug to the README 3 years ago
Graham 0a92ce020d Fix warning about deprecated SNAKE_CASE constant 3 years ago
Graham 22c1b8a183 Use useTempFile extension method in atomicWrite 3 years ago
Graham 0b43e26d36 Remove redundant toList() calls after sorted() 3 years ago
Graham 0ebe85ae39 Add missing comma to client-parameters.md 3 years ago
Graham 37b019e4a0 Fix heading levels in CONTRIBUTING.md 3 years ago
Graham 223a181dbf Update dependencies 3 years ago
Graham 3882a4dac3 Update dependencies 3 years ago
Graham 7aeeb82a10 Update dependencies 3 years ago
Graham 49d8e03e70 Update IDEA code style XML 3 years ago
Graham e442c1f32e Add section about OpenRS2's Gitea instance to CONTRIBUTING.md 3 years ago
Graham 75b7845d44 Move contribution guidelines to a separate file 3 years ago
Graham a53fb3164f Add link to the issue tracker 3 years ago
Graham 875301a713 Update Gradle 3 years ago
Graham 6d1685f152 Update dependencies 3 years ago
Graham a86f000f88 Add StreamCipher interface and a NopStreamCipher implementation 4 years ago
Graham c10ab13181 Update dependencies 4 years ago
Graham 59f48e3859 Remove @JvmSuppressWildcards on multibindings 4 years ago
Graham 771e36da02 Update dependencies 4 years ago
Graham bc14f19290 Update dependencies 4 years ago
Graham 6a800c0759 Fix ByteBuf leak in Js5ResponseDecoder 4 years ago
Graham e1404a1f7f Add missing release() call 4 years ago
Graham 35e3be4120 Update dependencies 4 years ago
Graham 80c7f996ea Update to Guice 5.0.0 beta 4 years ago
Graham 18a8f458fc Update dependencies 4 years ago
Graham e22765aada Remove redundant dependency 4 years ago
Graham f2400117fd Fix typo (are -> area) 4 years ago
Graham fc1af29bdf Make BATCH_SIZE const 4 years ago
Graham ac9b132937 Maintain static member grouping during deobfuscation 4 years ago
Graham e08f355e6f Use default allocator in testDecodeFragmented 4 years ago
Graham 76977303b2 Add shorthand methods for creating ByteBufs from ByteArrays and Strings 4 years ago
Graham 5036eb3da8 Add JS5 remote protocol implementation 4 years ago
Graham 0e706bc578 Update coverage pattern 4 years ago
Graham 678d906bdb Update dependencies 4 years ago
Graham 8b605532f5 Refactor some model-related code 4 years ago
Graham 808db2933b Rename list variable in MapElementList 4 years ago
Graham 039aea7eb6 Fix isGroupReady name 4 years ago
Graham 83ed779b1e Refactor HintArrow 4 years ago
Graham d8683b0773 Refactor more sprite-related code 4 years ago
Graham ce4b7022e2 Refactor some 2d rasterization code 4 years ago
Graham 6747ea183b Update Gradle 4 years ago
Graham c60dc8b15d Update dependencies 4 years ago
Graham 0a1b57e2db Refactor {Color,Monochrome}ImageCache 4 years ago
Graham a95940f0f0 Rename HookRequest 4 years ago
Graham 42b87026e6 Refactor ServerActiveProperties 4 years ago
Graham da9ff260ce Refactor ClanMember 4 years ago
Graham ea9be4826e Refactor SecondaryLinkedListIterator 4 years ago
Graham eb5ddb9589 Revert LoginManager.type's name 4 years ago
Graham ce1a3d6004 Refactor Buffer::getStringLength 4 years ago
Graham d8a98b1409 Update dependencies 4 years ago
Graham f143eef142 Move Kotlin files from src/{main,test}/java to src/{main,test}/kotlin 4 years ago
Graham ce5b76ef0f Rename dev.openrs2 package to org.openrs2 4 years ago
Graham 6cda83b4bc Map old Git author emails to openrs2.org 4 years ago
Graham 159df1eda7 Replace openrs2.dev with openrs2.org 4 years ago
Graham e7729f7dea Refactor more sound code 4 years ago
Graham 23a2868abb Update dependencies 4 years ago
Graham 64a570f965 Update dependencies 4 years ago
Graham 18a014cf17 Exclude the canvasScale field created by HighDpiTransformer 4 years ago
Graham b337698106 Fix MacResizeTransformer when it is used from the deobfuscator 4 years ago
Graham c276610a6d Add high DPI support to the client 4 years ago
Graham a7c9f3eb17 Update dependencies 4 years ago
Graham 3ca22ad180 Make Container extend DefaultByteBufHolder 4 years ago
Graham 9770bab86d Fix linter error 4 years ago
Graham 23b696f148 Exclude instanced map squares in the name hash generator 4 years ago
Graham d755d486d6 Add initial cache and XTEA key archiving service 4 years ago
Graham abac785456 Refactor MidiDecoder 4 years ago
Graham 2482e20063 Add BufferModule for injecting ByteBufAllocator 4 years ago
Graham 006b4ff5bc Update ASM 4 years ago
Graham d57797fda2 Add initial Song refactor 4 years ago
Graham 2f32b19205 Refactor Light and LightingManager 4 years ago
Graham c5142cc51b Refactor more Material class names 4 years ago
Graham 2a0574ba02 Refactor UnlitMaterial 4 years ago
Graham f03f7f9543 Refactor material code 4 years ago
Graham 5aa92fd600 Add JSON module 4 years ago
Graham 08afa71d30 Update dependencies 4 years ago
Graham 9ed4016a32 Refactor some WorldMap code 4 years ago
Graham 3ba8311199 Refactor more utility methods 4 years ago
Graham 9758833251 Refactor some CharUtils methods 4 years ago
Graham a95b13220a Refactor the DisplayMode class 4 years ago
Graham 30b502f87f Refactor some mapscene-related code 4 years ago
Graham 485ea2621c Refactor WorldMapFont 4 years ago
Graham 1988623824 Refactor the GlProgram class 4 years ago
Graham 59f23f4743 Improve Loc refactoring 4 years ago
Graham 0eabc5b28f Rename the RawModel class 4 years ago
Graham f303f836e3 Refactor ByteArraySecondaryNode 4 years ago
Graham 24cb9ee211 Rename BoundingBox 4 years ago
Graham 0744f74c65 Rename primary/secondary/tertiary intArg to 1/2/3 4 years ago
Graham 40b7a6f17b Rename SceneGraphNode to ParticleNode 4 years ago
Graham 8c32ae1769 Rename the Font class 4 years ago
Graham 6cf0b8dce2 Refactor StockMarketOffer 4 years ago
Graham eeefe628dc Add initial particle system refactor 4 years ago
Graham 12be5a7228 Refactor world list, account creation and login code 4 years ago
Graham 53a897098f Refactor ByteArrayNode 4 years ago
Graham 40018e103d Refactor the ReflectionCheck class 4 years ago
Graham 1acf5ba8d9 Refactor VarcDomain, VarpDomain and DelayedStateChange 4 years ago
Graham 8401126775 Refactor more quick chat code 4 years ago
Graham 952678af0d Refactor QuickChatPhraseTypeList and QuickChatCatTypeList 4 years ago
Graham 60bafd7543 Refactor the PathFinder 4 years ago
Graham fb8f8d1585 Rename readPlayerExtendedInfo to readExtendedPlayerInfo for consistency 4 years ago
Graham f74fa5793e Rename StackFrame to GoSubFrame 4 years ago
Graham d456e79cd6 Update dependencies 4 years ago
Graham 51d6763be1 Refactor MelTypeList 4 years ago
Graham b5c23daedb Refactor MsiTypeList 4 years ago
Graham 2b400e4290 Refactor CursorTypeList 4 years ago
Graham 4e0939d1bb Refactor SpotAnimTypeList 4 years ago
Graham faa6b0b619 Refactor IdkTypeList 4 years ago
Graham 5074405a5c Refactor SeqTypeList 4 years ago
Graham 5c135c7ced Refactor more config archive types 4 years ago
Graham 08e41fc31c Refactor inbound packet reading methods 4 years ago
Graham 991158f8a9 Move Player/NPC lists to their own classes 4 years ago
Graham 25656d4165 Refactor some terrain-related code 4 years ago
Graham 8a459ddd47 Disable externalDocumentationLinks unless a project property is set 4 years ago
Graham 38f978c4a5 Rename EnumInverseNode to EnumStringEntry 4 years ago
Graham 10be6e4b73 Rename SpotAnim, ProjAnim and Model 4 years ago
Graham ef37703a9b Refactor some texture, sprite and rendering-related code 4 years ago
Graham 2bcff4bb06 Refactor more of the ScriptRunner class 4 years ago
Graham a0c07b294b Refactor Player.self 4 years ago
Graham af73e783a9 Refactor Cs1ScriptRunner 4 years ago
Graham 578ed9a205 Add initial ScriptRunner refactor 4 years ago
Graham b893a27ade Rename fastReadString to readStringFast for consistency 4 years ago
Graham 78cf794ead Refactor ClientScript class 4 years ago
Graham 4819780815 Add initial InterfaceList refactor 4 years ago
Graham d64a657543 Rename AwtMouseWheel to JavaMouseWheel 4 years ago
Graham 4e22842a75 Refactor VarpType and VarbitType 4 years ago
Graham f882568686 Rename stop to quit for consistency 4 years ago
Graham 77970f4681 Partially refactor the AudioChannel class 4 years ago
Graham 9e12bce93c Refactor EnumType 4 years ago
Graham c9b53f3aa6 Rename the Entity class 4 years ago
Graham 5c5ca5730f Refactor the ObjStack class 4 years ago
Graham fd137191e7 Refactor Inv and InvType 4 years ago
Graham 2c0093a633 Refactor the BufferPool class 4 years ago
Graham 4f7192b9e6 Refactor the FrameBuffer class 4 years ago
Graham 73c41d7302 Rename tick to loop 4 years ago
Graham 528deec61f Refactor the MouseWheel class 4 years ago
Graham 531965b504 Refactor the Keyboard and Mouse classes 4 years ago
Graham f088664ac9 Refactor MouseRecorder 4 years ago
Graham bce63109ec Fix name of readEncryptedBytes 4 years ago
Graham df3370c0ef Rename skullIcon to pkIcon 4 years ago
Graham 10112f5112 Partially refactor LocType 4 years ago
Graham 0e81bc211b Refactor StructType 4 years ago
Graham 0e7e8f2d7d Make the individual components of an XteaKey public 4 years ago
Graham 2c7abdc7ff Use bufferedReader() extension method 4 years ago
Graham efc7ba9d2b Add underflow/overflow tests 4 years ago
Graham 06cad72eaa Add Js5Archive object 4 years ago
Graham 977dd0bd93 Check that we read exactly uncompressedLen bytes 4 years ago
Graham cdae86aad6 Truncate existing output files in the compression commands 4 years ago
Graham 107c432e65 Rename NONE to UNCOMPRESSED 4 years ago
Graham 8d35b5010a Add functions for quickly checking if a XTEA key is valid 4 years ago
Graham 88175e798f Remove debug println from Js5IndexTest 4 years ago
Graham fabe7983df Update unpackclass.yaml 4 years ago
Graham cf34a04b80 Move skyboxTextures to a Defaults class 4 years ago
Graham 0bbf6b9507 Rename Js5ResourceProviderImpl to Js5CachedResourceProvider 4 years ago
Graham 44adebd9be Refactor the sorted WorldList 4 years ago
Graham 3ffd7caaa1 Refactor the WorldList class 4 years ago
Graham 24f094b387 Refactor decodeDefaults 4 years ago
Graham 2a6e50d44d Refactor the WordPack class 4 years ago
Graham 946fa2c3db Rename IntegerUtils to IntUtils 4 years ago
Graham 9907a7d2c3 Renmae clearSoft() to removeSoft() 4 years ago
Graham cc17a25f01 Refactor LruHashTable 4 years ago
Graham 41ad780a65 Rename LruHashTable to SoftLruHashTable 4 years ago
Graham 7d626bcabb Rename sweep() to clean() 4 years ago
Graham 0c020c7f36 Rename Js5Index::read to decode 4 years ago
Graham 38859a2c18 Refactor NpcType, ObjectType and associated code 4 years ago
Graham ae70bbb804 Update dependencies 4 years ago
Graham 22ec916000 Refactor Js5 instance names 4 years ago
Graham 4354b17ddf Update gradle-versions-plugin 4 years ago
Graham 8b33989768 Update clikt 4 years ago
Graham df6c9a610c Create singleton sets in LiveVariableAnalyzer where possible 4 years ago
Graham 15d359429e Add alloc parameter to Store.open() 4 years ago
Graham d2174bb77c Improve checksum/digest terminology in the README 4 years ago
Graham 1cce3b33d0 Replace non-breaking spaces with normal spaces in the README 4 years ago
Graham eb7f71a7a4 Update Dokka 4 years ago
Graham a2d41d924f Change cache's crypto dependency to an API dependency 4 years ago
Graham 89accc8b26 Rename root to ROOT 4 years ago
Graham 0afc7a563d Add Store.open() method 4 years ago
Graham 5a20b75f4f Use Preconditions.checkPositionIndexes() in ByteBuf.crc32() 4 years ago
Graham d2ee9d3b8b Check that all decompression algorithms throw IOException on failure 4 years ago
Graham 0d384fa8f2 Throw IOException for all JS5 decompression errors 4 years ago
Graham 78f8069177 Add method for stripping the version trailer from a ByteBuf 4 years ago
Graham e764403cb2 Add tests for corrupt containers 4 years ago
Graham b51b2f5e77 Cache values() array in enums 4 years ago
Graham a4234d066d Add CRC-32 extension method to the ByteBuf class 4 years ago
Graham 7887a4dff1 Rename Resource::crc to checksum 4 years ago
Graham f9c14aad14 Remove redundant rollback after a successful commit 4 years ago
Graham 3ee8005ab7 Update clikt 4 years ago
Graham d5240e3e42 Use assertions for conditions we never expect to hit 4 years ago
Graham 3a39639e3d Add database API 4 years ago
Graham ec4f8b59c9 Use default ByteBufAllocator in unit tests 4 years ago
Graham 4fdedf298b Add support for colliding name hashes to Js5Index 4 years ago
Graham f984f357d8 Add Js5Index implementation 4 years ago
Graham 8bed0fc875 Rename getOrCreateIndex to createOrGetIndex 4 years ago
Graham 6fb19e402b Fix typo (discardUnapcked -> discardUnpacked) 4 years ago
Graham bd010668dd Add method chaining to ByteBufExtensions 4 years ago
Graham b44bb21403 Add public keyword to new Kotlin class declarations 4 years ago
Graham 0af11d75c3 Add flag to disable encryption of uncompressed containers by default 4 years ago
Graham 9aaa095b02 Use DIGESTBYTES to set the size of the output ByteArray 4 years ago
Graham 380cc95d31 Make DIGESTBYTES public 4 years ago
Graham e5e9ece098 Fix JS5 uncompression of large GZIP-compressed files 4 years ago
Graham 415363fbbe Pass --delete to rsync 4 years ago
Graham 434960eeec Add KDoc link to the README 4 years ago
Graham fceab7ee1e Remove dokkaHtmlCollector from the build task 4 years ago
Graham a235167b89 Publish KDoc to docs.openrs2.dev 4 years ago
Graham 06106938af Replace project.rootDir with rootDir 4 years ago
Graham 2520798ccf Move commitHash() to the bottom of build.gradle.kts 4 years ago
Graham 8cbf973f50 Update Dokka to 1.4.0-rc 4 years ago
Graham d8f98c1bf5 Update data file availability point in the FAQ 4 years ago
Graham 7ab567b7b9 Optimise DiskStore::list 4 years ago
Graham 6314f3625a Add ByteBuf extension methods for reading/writing smarts 4 years ago
Graham 4b1c6c7cb7 Rename Smart to ShortSmart 4 years ago
Graham 192d5b9e8d Resolve inspections 4 years ago
Graham b2a7464da2 Use Kotlin's new ArrayDeque class 4 years ago
Graham ab642f300d Remove explicit stdlib dependency 4 years ago
Graham 1455379f20 Document thread safety of DiskStore and FlatFileStore 4 years ago
Graham 20d6d73d30 Check {read,write}BufferSize are both zero or positive 4 years ago
Graham 5e447f31d6 Free readBuffer if the writeBuffer allocation fails 4 years ago
Graham 8cdd33a1dd Replace exists() with isDirectory() for consistency 4 years ago
Graham 3f8e1a1951 Add FlatFileStoreTest 4 years ago
Graham 8c93b4623d Format .editorconfig 4 years ago
Graham 232ba32bbd Enable explicit API mode 4 years ago
Graham ed2a3f6dc6 Update Kotlin 4 years ago
Graham 0546e05183 Update kotlinter/ktlint 4 years ago
Graham 9f1b2dbc29 Add BufferedFileChannel 4 years ago
Graham 1e1711820d Rename actualSeq to actualBlockNum 4 years ago
Graham 440e59ee00 Update Gradle 4 years ago
Graham 2dda87fa4a Improve testListGroups() 4 years ago
Graham ad5f714982 Add testBounds() to DiskStoreTest 4 years ago
Graham 9de0b26da4 Throw exception in remove() if group ID is negative 4 years ago
Graham 63a8f5534e Add netty-buffer API dependency to the cache module 4 years ago
Graham ada90fb027 Add DiskStore tests 4 years ago
Graham e8e2460d8e Rename getDelta to getDuration 4 years ago
Graham c634d2e4d7 Rename seq to blockNum 4 years ago
Graham cea016d4ef Add low-level cache interface 4 years ago
Graham cec68723a4 Add Cp1252Charset contains test 4 years ago
Graham eef8fc1f0c Improve Cp1252Charset.contains() 4 years ago
Graham 2525501901 Add ModifiedUtf8Charset 4 years ago
Graham 7ab3b3d335 Add method for calculating the K&R hash code of a CP-1252-encoded string 4 years ago
Graham be7cc9ac8a Add Cp1252Charset 4 years ago
Graham e7ad4b92ff Group test files by test 4 years ago
Graham 7179743f5d Add fsync() extension method 4 years ago
Graham ef02a687c9 Update fastutil 4 years ago
Graham 0f032e4f21 Update to Gradle 6.6 4 years ago
Graham c21702a0d5 Refactor the insertBefore method 4 years ago
Graham c3d7e9d081 Add reference counting documentation 4 years ago
Graham d801e5fda6 Re-use length variables in the Js5Compression code 4 years ago
Graham db3421418c Fix prefetch/len local names in Js5NetQueue 4 years ago
Graham 24db41a74e Add gzip headers to containers 4 years ago
Graham ce97775663 Reduce use of ByteBuf{Input,Output}Stream in Js5Compression 4 years ago
Graham 8887b0a1e9 Refactor the crc32 method 4 years ago
Graham 3f59e1764c Store individual XteaKey components separately 4 years ago
Graham 58335ca6d0 Add JS5 compression/encryption implementation 4 years ago
Graham f81f4a81c7 Set LzmaCommand's default level to -6 4 years ago
Graham 921ef3a08f Refactor some of the client loading code 4 years ago
Graham 10a99fc9fb Refactor most of the preferences code 4 years ago
Graham cceaf59d3c Refactor the Base37 encoding/decoding methods 4 years ago
Graham 4f18ca0947 Refactor JS5 connection code 4 years ago
Graham 1bb5cb4e06 Refactor Js5ResourceProvider and associated code 4 years ago
Graham 07e5a6f488 Refactor the Js5MasterIndex class 4 years ago
Graham 5144604f2d Rename getReadyPercentage to getPercentageComplete 4 years ago
Graham 4b1fdcf716 Refactor JS5 request queues 4 years ago
Graham 6aac893a1f Rename Js5Index to Js5 in unpackclass 4 years ago
Graham 026ffb21c4 Refactor the client's Js5 class 4 years ago
Graham 693ed579e8 Refactor more of the GameShell class 4 years ago
Graham 4616401987 Refactor Timer classes and associated code in GameShell 4 years ago
Graham 4e4b0aad40 Refactor the HuffmanCodec class 4 years ago
Graham e689d65f6c Refactor the SecondaryHashTable class 4 years ago
Graham 1e4e48e413 Rename iteratorKey to iteratorBucket 4 years ago
Graham 946ab98a82 Refactor the HashTableIterator class 4 years ago
Graham 9ffc7701e2 Refactor the secondary insertAfter method 4 years ago
Graham a8c13614ef Refactor the ReferenceNodeFactory class 4 years ago
Graham 2dfd3ed889 Refactor the LruHashTable class 4 years ago
Graham 7590fa8d7c Refactor the ReferenceNode class 4 years ago
Graham aeb03865a1 Refactor the SecondaryLinkedList class 4 years ago
Graham 9b81f5c6c0 Refactor the SecondaryNode class 4 years ago
Graham 1ae491d411 Refactor the HashTable class 4 years ago
Graham ceb3e0d702 Refactor the client's LinkedList class 4 years ago
Graham 6c66a4cee5 Refactor the client's ArrayUtils class 4 years ago
Graham c0703c9065 Refactor the Cache class 4 years ago
Graham 5af09c9c4b Refactor the BufferedFile class 4 years ago
Graham 77aa69fe97 Refactor the BufferedSocket class 4 years ago
Graham a057750792 Refactor the Broken{Input,Output}Stream classes 4 years ago
Graham 73ebb64c73 Refactor the IntNode and StringNode classes 4 years ago
Graham 1cc0fcc8b7 Refactor the client's TracingException class 4 years ago
Graham c819e85574 Refactor the client's bzip2 decompressor 4 years ago
Graham 01a3bc020d Refactor the client's GzipDecompressor class 4 years ago
Graham 31de41283f Refactor the Js5Compression class 4 years ago
Graham 646af10bb1 Refactor StringUtils and Cp1252Charset 4 years ago
Graham 392c43562c Refactor the client's Js5Index class 4 years ago
Graham a18072f542 Refactor hostname/port selection code 4 years ago
Graham 9b63ae48e1 Refactor the sleep method 4 years ago
Graham 0350f7fe02 Refactor most of the GameShell class 4 years ago
Graham 9a748035c7 Refactor the BrowserControl class 4 years ago
Graham d2bf5864be Refactor the client's main method 4 years ago
Graham d0f29f7b26 Refactor the localisation code 4 years ago
Graham 3f3c27f354 Refactor the Packet class 4 years ago
Graham 960da043d6 Refactor the IsaacRandom class 4 years ago
Graham 57d6ec2e63 Add initial refactor of the client's Buffer class 4 years ago
Graham 95b9d35f1a Refactor the client's IntHashTable class 4 years ago
Graham 2dfb2326c1 Refactor value variable in readShort 4 years ago
Graham ab26217db4 Refactor unpackclass 4 years ago
Graham 7c862f4079 Refactor the Js5File class 4 years ago
Graham e163483536 Refactor unpackclass's Js5Index class 4 years ago
Graham c03b4af44f Refactor unpackclass's ArrayUtils class 4 years ago
Graham 327a1f7d4d Refactor unpackclass's IntHashTable class 4 years ago
Graham 7687a08cf7 Refactor unpackclass's LinkedList class 4 years ago
Graham 856a17bf02 Refactor unpackclass's gzip decompressor 4 years ago
Graham 887ce06302 Refactor unpackclass's Bzip2 decompressor 4 years ago
Graham 6a0976193d Change Resources field names to upper case 4 years ago
Graham e0ebaa1bcd Refactor unpackclass's Buffer and BufferPool classes 4 years ago
Graham af0167ef3e Refactor unpackclass's TracingException class 4 years ago
Graham 628bdc2e86 Refactor the TracingException class 4 years ago
Graham 28f889fe6a Refactor the UnpackerClassLoader class 4 years ago
Graham cd043a7ddc Refactor the loader class 4 years ago
Graham d23f694a75 Refactor the Resource class 4 years ago
Graham 8e592431bc Refactor remaining local variables and arguments in the GL library 4 years ago
Graham 0ec7024c2c Refactor the AudioSource interface 4 years ago
Graham fe76054d83 Refactor the MonotonicClock class 4 years ago
Graham 7ff9343516 Refactor the FileOnDisk class 4 years ago
Graham 6e6297b094 Refactor the CursorManager class 4 years ago
Graham 3549b7d6ea Refactor the FullScreenManager class 4 years ago
Graham 79d8d9f78c Refactor the SignLink and PrivilegedRequest classes 4 years ago
Graham 447e667733 Refactor the Pack200Unpacker class 4 years ago
Graham 64da80403e Refactor the unpack class 4 years ago
Graham a75d20f6fd Refactor the ByteArray and DirectByteArray classes 4 years ago
Graham 201f4cbd46 Refactor the Node class 4 years ago
Graham 0e9148af01 Add initial deob maps 4 years ago
Graham 2acbfdce48 Add divide support to IdentityTransformer 4 years ago
Graham 222323eaa0 Add AssignExpr support to IdentityTransformer 4 years ago
Graham c98a24bff8 Add left shift support to BitMaskTransformer 4 years ago
Graham 47325edb72 Fix removal of unused arguments whose local variable slot is re-used 4 years ago
Graham 1eb423fbf6 Mark sbox as const 4 years ago
Graham 272b1eb650 Add support for removing an argument whose local variable slot is used 4 years ago
Graham 2b8aa0c70d Replace some primitive casts with widening/narrowing conversions 4 years ago
Graham b642bb17c6 Move literals to the RHS of an add/sub expression 4 years ago
Graham c4df34102a Run IfElseTransformer until we reach a fixed point 4 years ago
Graham 0ae1ffb51d Don't convert integer literals in char arithmetic to char literals 4 years ago
Graham ea9f212fe1 Reduce indentation of if/else blocks where possible 4 years ago
Graham 90c21d87cf Replace nested ifs with && 4 years ago
Graham a75db6122b Return IfStmt from getIf() 4 years ago
Graham 1833221bc8 Fix naming of Module constants 4 years ago
Graham 717d14b489 Rename DeobfuscatorMapModule to DeobfuscatorUtilModule 4 years ago
Graham 4bc11159d8 Add NotTransformer 4 years ago
Graham b20115f1a7 Simplify IfElseTransformer 4 years ago
Graham 9c80ff1c6e Fix conversion of negative integers to char literals 4 years ago
Graham d9922da1c5 Add missing @Singleton annotations 4 years ago
Graham 2aee8523c7 Optimise CharLiteralTransformer 4 years ago
Graham 880be759b7 Use smart casts in the AST deobfuscator 4 years ago
Graham bde818230b Use smart casts in hasSideEffects() 4 years ago
Graham 8f0dfd2725 Fix FieldAccessExpr's hasSideEffects() implementation 4 years ago
Graham 919d9c2386 Add ThisExpr to hasSideEffects() 4 years ago
Graham 1318cce216 Move this keyword to the right-hand side of comparisons 4 years ago
Graham 9cd19e3708 Add HexLiteralTransformer 4 years ago
Graham 63223b1fed Update dependencies 4 years ago
Graham 68b2f67522 Convert integers to char literals 4 years ago
Graham 12a0f17e59 Replace ternaries in an else block with another if/else 4 years ago
Graham c16295d72c Add LZMA preset option constants 4 years ago
Graham 95c97b8399 Add XteaKey inline class with ZERO "constant" and isZero method 4 years ago
Graham ce4de919b8 Use infix version of shl 4 years ago
Graham 1a0b7c1d33 Add Whirlpool implementation 4 years ago
Graham 8e3452ef98 Improve IfElseTransformer comments 4 years ago
Graham bd28638b64 Update dependencies 4 years ago
Graham f5ae71932d Add LZMA compression algorithm 4 years ago
Graham 29b63b613a Use > and >= in for loops that decrement instead of increment 4 years ago
Graham ba67f32d42 Format XteaTest 4 years ago
Graham 504e832872 Improve AddSubTransformer 4 years ago
Graham d33c03c708 Improve Expression.negate() 4 years ago
Graham e0708458f9 Add use extension method for releasing reference-counted objects 4 years ago
Graham 0eda020092 Update dependencies 4 years ago
Graham d305e1f41c Retain (T[]) casts on a variadic T... argument 4 years ago
Graham d1b8fbd094 Remove redundant casts 4 years ago
Graham 9a4401c678 Update dependencies 4 years ago
Graham 1308819561 Remove unused local variables of all types 4 years ago
Graham ca419eecb5 Add CopyPropagationTransformer 4 years ago
Graham 4da1c5c3cf Fix XTEA en/decryption if the index is not a multiple of the block size 4 years ago
Graham 7218855eb7 Fix en/decryption of XTEA messages not a multiple of the block size 4 years ago
Graham 73194bbb8d Exclude dev releases in rejectVersionRegex 4 years ago
Graham 07433ccef0 Update dependencies 4 years ago
Graham da8ddd00be Update Gradle 4 years ago
Graham 4c6f6b07cf Store mapped local variable names in the OriginalPcTable 4 years ago
Graham f291b121bc Add TryCatchBlockNode::remap for consistency 4 years ago
Graham 8dd5292cb2 Remap argument names 4 years ago
Graham 33955a57ad Rename ArgRef to ArgPartition 4 years ago
Graham f4d8b29747 Rename signlink to signLink for consistency 4 years ago
Graham 9331e78bba Fix exclude-nonfree scope 4 years ago
Graham a87a289c49 Add duplicate class name support to the AST deobfuscator 4 years ago
Graham f9021f7fe6 Use Module class in the decompiler 4 years ago
Graham b5cbe3ea70 Add class for tracking dependencies between modules 4 years ago
Graham 35e4e035ef Rename deob-map to deob-util 4 years ago
Graham 3a70333380 Update dependencies 4 years ago
Graham 8a563a3b26 Remap InnerClassNodes 4 years ago
Graham 46c0c29559 Set AstDeobfuscator's language level to 11 4 years ago
Graham da193ebf52 Use platform class loader to resolve JDK types in the AstDeobfuscator 4 years ago
Graham c3715d7320 Add useTempFile extension methods 4 years ago
Graham 8370ad104e Write files atomically 4 years ago
Graham 45d59101b2 Add outer class/method support to ClassNodeRemapper 4 years ago
Graham 882cbed44f Add duplicate class name support to the deobfuscator 4 years ago
Graham 7811bc70f4 Use IdentityHashMap to store original instruction indexes 4 years ago
Graham c57f0927ba Update shadow plugin 4 years ago
Graham 58a061a7d8 Update dependencies 4 years ago
Graham 57800cb03e Exclude browser control class from static scrambling 4 years ago
Graham 3db6b2c381 Add Any{Class,Member}Filter 4 years ago
Graham 26d3c79b73 Shorten method names in OriginalNameTransformer 4 years ago
Graham fa2f9cd33d Add scrambledLibraries set to the deobfuscator profile 4 years ago
Graham 9db64efb7b Add name field to Library 4 years ago
Graham 091c0ed29e Decouple Library{Reader,Writer} from the Library class entirely 4 years ago
Graham 72a0642db3 Rename Js5Library{Reader,Writer} to PackClassLibrary{Reader,Writer} 4 years ago
Graham 7903e547dd Sort input/output jar lists 4 years ago
Graham 5c2ac92475 Improve log message consistency 4 years ago
Graham 611ac6de84 Add initial NameMap support to TypedRemapper 4 years ago
Graham d28c712299 Deobfuscate a single client at a time 4 years ago
Graham 79b0103a06 Return empty NameMap if the share/deob/map directory does not exist 4 years ago
Graham b946615169 Combine the new name map with the previous map 4 years ago
Graham 27f715ce13 Create parent directories in NameMapProcessor 4 years ago
Graham 327a6f0e3f Allow final modifier to replace volatile modifier 4 years ago
Graham 64d1b836ed Remove dead code before performing final field analysis 4 years ago
Graham 6c26fb8b9c Fix incorrect marking of fields as final if no constructor existed 4 years ago
Graham d410f87240 Consider static and instance fields separately in FieldWriteAnalyzer 4 years ago
Graham 2cbe6ce8e0 Rename UNKNOWN to ONCE_OR_MORE 4 years ago
Graham 8254f3d69f Improve FieldWriteAnalyzer 4 years ago
Graham 37c672a9bf Add support for overriding inSets of entry nodes to DataFlowAnalyzer 4 years ago
Graham 38f731f986 Mark fields as final where possible 4 years ago
Graham eee96d4c3e Update Gradle 4 years ago
Graham deaeea2752 Rename inlineConstantArgs to propagateConstantArgs 4 years ago
Graham 147744771c Skip propagation of constant arguments in non-renamable methods 4 years ago
Graham c03673a1e8 Add interface support to EmptyClassTransformer 4 years ago
Graham 9faa519e86 Use active voice in the sentence about nonfree being .gitignored 4 years ago
Graham ea82565cd6 Update Bouncy Castle 4 years ago
Graham 7740b13a29 Remove unused classAccess argument from getVisibility 4 years ago
Graham 0bd10234b8 Run RemapTransformer as early as possible 4 years ago
Graham 71413256da Check that <clinit> methods have a single tail RETURN 4 years ago
Graham 7d9b1c61e8 Combine the StaticScramblingTransformer with the RemapTransformer 4 years ago
Graham cccbbe4d19 Implement map{Field,Method}Owner and getFieldInitializer 4 years ago
Graham 4f17d67039 Use mapType() instead of map() in ClassForNameUtils for consistency 4 years ago
Graham 4674dfb5ba Remove FieldInitializer class 4 years ago
Graham 35fea8f293 Remove redundant substring() call 4 years ago
Graham 676be4a0cb Add XTEA implementation 4 years ago
Graham eef6d31753 Replace org.junit.jupiter.api.Test with kotlin.test.Test 4 years ago
Graham 7fc8c4e078 Add ISAAC cipher implementation 4 years ago
Graham cbb7e8c086 Make AbstractInsnNode::remap public 4 years ago
Graham 325ce27a31 Add seperate {Field,Method}Node::remap extension methods 4 years ago
Graham a3ec4ec322 Add getFieldInitializer to ExtendedRemapper 4 years ago
Graham 1a41982b76 Rename desc to descriptor in ExtendedRemapper 4 years ago
Desetude ce5a0464c7 Only run NewInstanceTransformer on Class references 4 years ago
Graham fc718a7672 Use partitions in TypedRemapper maps 4 years ago
Graham 9b1f0c6886 Split TypedRemapper::create up 4 years ago
Graham 113820a9a2 Add ExtendedRemapper type with support for moving fields/methods 4 years ago
Graham 64dc49e15b Delete empty <clinit> methods 4 years ago
Graham 524a894df2 Revert "Simplify static field scrambling" 4 years ago
Graham d61411571b Move return outside use block 4 years ago
Graham 46dd61fc5d Add PATH constant to ProfileProvider 4 years ago
Graham 69a27e43d4 Move OpenGL registry to the share/deob folder 4 years ago
Graham 6d6287c917 Inject GlRegistry 4 years ago
Graham 3c9edb0291 Use dependency injection in the AST deobfuscator 4 years ago
Desetude 5c8b031719 Check if method is declared by interface in VisibilityTransformer 4 years ago
Graham 04a919fffa Update dependencies 4 years ago
Graham 364a890f08 Update Gradle 4 years ago
Graham 620d608ecb Update dependencies 4 years ago
Graham 88bed9923b Update clikt 4 years ago
Graham 346cecf885 Update dependencies 4 years ago
Graham d11151a908 Update dependencies 4 years ago
Graham e500d8d211 Set shadow distribution base name to openrs2 4 years ago
Graham 554e643e88 Rename the fat jar inside the shadow distribution to openrs2.jar 4 years ago
Graham 1b49f6c2e1 Disable the main distribution 4 years ago
Graham 99a28a842c Add dependency on the .git directory to the dokka task 4 years ago
Graham 06b1b78952 Use shorthand rootDir and buildDir methods 4 years ago
Graham 18a6736b68 Add AUTHORS file to the distribution 4 years ago
Graham 4e7b107954 Quote strings in config.example.yaml 4 years ago
Graham dd06bb2f83 Remove native library loading logic for SunOS/Solaris 4 years ago
Graham 040fc87f0d Remove redundant type 4 years ago
Graham ccf7056a93 Use private companion objects where possible 4 years ago
Graham deac3306e8 Move share/deob-map to share/deob/map 4 years ago
Graham 3db4c82569 Load Profile from a YAML file 4 years ago
Graham 3dcf91cfef Move MemberRefKeyDeserializer to the asm module 4 years ago
Graham 2a4d607d96 Remove support for escape characters from glob patterns 4 years ago
Graham e13cc5b47e Convert YAML property names to snake case 4 years ago
Desetude c99ebcb355 Add logging to the AST deobfuscator 4 years ago
Graham 9c10df5e2c Add CRC-32 values to the list of non-free files 4 years ago
Graham 5c9a6c2abe Deobfuscate the jaggl jar 4 years ago
Graham bc52f061fb Don't union <init> method sets together 4 years ago
Graham 5af43165ff Replace TypedRemapper constants with a more flexible system 4 years ago
Graham f4314ca3cb Update Gradle 4 years ago
Graham d1aa625397 Move build.gradle.kts files out of the nonfree repository 4 years ago
Graham 0d4daca1a1 Add deob-map dependency to the deobfuscator 4 years ago
Graham 3b48e57679 Add Guava dependency to deob-ast 4 years ago
Graham e25918a8a0 Add NameMapProvider 4 years ago
Graham 435dee8b6d Add MemberRefKeyDeserializer 4 years ago
Graham e8ecd5016d Set version to 0.1.0-SNAPSHOT 4 years ago
Graham 7e422447ef Convert hasCode() to a property 4 years ago
Graham 9de55399f5 Convert createLong to an extension method on Long 4 years ago
Graham cf3474c016 Convert createIntConstant to an extension method on Int 4 years ago
Graham 2471418d4a Add is prefix to boolean extension properties 4 years ago
Graham 47d1bc0bd2 Convert stackMetadata to an extension property 4 years ago
Graham b6f7576864 Add DeobfuscatorProcessorModule 4 years ago
Graham 971437e142 Add skeleton Js5LibraryReader 4 years ago
Graham 06212b8511 Add shorthand Library read/write methods 4 years ago
Graham ae7c19beac Convert most Library{Reader,Writer} implementations to objects 4 years ago
Graham 17bc1e287b Move common jar reading logic to a separate class 4 years ago
Graham 2f3cdca12d Reduce use of temporary files 4 years ago
Graham 0206bbd4b2 Set DeterministicJarOutputStream's default compression level to 9 4 years ago
Graham 451a1d9c5e Use a single Resource::compressLibrary method for all writers 4 years ago
Graham daefa7f4ea Remove streams from LibraryReader and LibraryWriter constructors 4 years ago
Graham 46d8af509c Create bundler output directory if it doesn't exist 4 years ago
Graham 8685d2b2dc Remove unused DeterministicJarOutputStream methods 4 years ago
Graham 10145fdb43 Add Sequence<JarEntry> extension property to JarInputStream 4 years ago
Scu11 6e877b52ce Move Library#read methods to dedicated classes 4 years ago
Scu11 26348b8a2e Move Library#write methods to dedicated classes 4 years ago
Graham b40eedfb3b Use block comment for multi-line comment in Deobfuscator 4 years ago
Graham 76f940e734 Remove unintentional static imports 4 years ago
Graham 3c051fe8c1 Rename private.key to game.key 4 years ago
Graham 21551b119c Add kotlin-reflect dependency 4 years ago
Graham 25f8bac502 Install example config file at the real path in the distribution 4 years ago
Graham 5e987f1f61 Remove memory allocation delay during client startup 4 years ago
Graham c488a155c4 Replace operator and game names with values from the config file 4 years ago
Graham ef5db18ea1 Replace runescape.com with domain from the config file 4 years ago
Graham fe3a8c1a37 Replace cachesubdir with the internal game name 4 years ago
Graham ed052c783b Use cache path and signer name from the config file 4 years ago
Graham 3aaea52eac Remove trailing whitespace from log messages 4 years ago
Graham 05a03d5bdc Inject transformers in the bundler and deobfuscator 4 years ago
Graham d07714a37d Update dependencies 4 years ago
Graham 0c76c610bd Convert Guice modules to objects 4 years ago
Graham d119cc07b5 Add config file parser 4 years ago
Graham d32ea82032 Inject ObjectMapper in NameMapProcessor 4 years ago
Graham 9441527c61 Add yaml module 4 years ago
Graham 2c4261e751 Exclude Guice annotations from auto-import 4 years ago
Graham c394b404b9 Update dependencies 4 years ago
Graham 5256f00ee0 Make libraryClasses return Sequence instead of List 4 years ago
Graham 49a90c1cf5 Rename getNode() to getClassNode() 4 years ago
Graham ff96cf70a2 Rename dokka output directory to kdoc 4 years ago
Graham e50616a565 Document MonitorTransformer 4 years ago
Graham 9a1c0e9caf Add DeterministicJarOutputStreamTest 4 years ago
Graham 958fbc03ba Add IterableUtilsTest 4 years ago
Graham 972d3cebc1 Add ForestDisjointSetTest 4 years ago
Graham a9aae4ea37 Update dependencies 4 years ago
Graham 1c539f22fe Add DisjointSet and ForestDisjointSet documentation 4 years ago
Graham de467837f0 Add externalDocumentationLinks 4 years ago
Graham e4724871d6 Document ExceptionTracingTransformer 4 years ago
Graham 4cfb577e48 Replace StandardCharsets with Charsets 4 years ago
Graham 609576be63 Add Dokka task to the root project 4 years ago
Graham 0c709bf0e6 Remove redundant constructor keyword 4 years ago
Graham 14b82dd9d8 Switch to -SNAPSHOT version of Fernflower 4 years ago
Graham 7ce21b0cde Update dependencies 4 years ago
Graham 31e9ac9170 Add InvokeSpecialTransformer documentation 4 years ago
Graham 54aa9b738c Add testFlush() to SkipOutputStreamTest 4 years ago
Graham dd560c1801 Restrict code coverage to the dev.openrs2 package 4 years ago
Graham 1d56887e03 Disable per-module working directory for coverage compatibility 4 years ago
Graham ab138f5572 Revert "Run tests with Gradle instead of IDEA's test runner" 4 years ago
Graham 34c7e049b1 Remove duplicate junit-jupiter-api dependency with different version 4 years ago
Graham fced5610a5 Add SkipOutputStreamTest 4 years ago
Graham 6e0844fa73 Update dependencies 4 years ago
Graham 77aa313b64 Only use StackFrameClassWriter where required 4 years ago
Graham 055efd60e7 Rename maxVersion() to max() 4 years ago
Graham bbe4157124 Add gte() method to ClassVersionUtils 4 years ago
Graham acce037eac Copy entire share directory into the distribution 4 years ago
Graham e6a92ea0aa Switch to FHS-style layout 4 years ago
Graham 24c8c43221 Use TEMP_PREFIX and JAR_SUFFIX constants in writeSignedJar 4 years ago
Graham d006997d2d Add annotation processor for creating deobufscator name mapping files 4 years ago
Graham c365019007 Output descriptor last in Member{Desc,Ref}.toString() 4 years ago
Graham 9760a98d95 Compile regex in DecompileCommand once 4 years ago
Graham 4f48a59ee3 Compile regex in verifyMapping() once 4 years ago
Graham 586ffabc57 Set artifactId of all submodule to openrs2 4 years ago
Graham 4cd1b7b457 Remove unused methods before unused arguments 4 years ago
Graham 0395224000 Simplify removeDeadCode() 4 years ago
Graham 5c1e630cb8 Add extension method for copying a MethodNode 4 years ago
Graham ea755497ee Optimize DataFlowAnalyzer by reducing the size of the initial work list 4 years ago
Graham 2ac0e7eb50 Fix typo in comment (deobfuscator -> obfuscator) 4 years ago
Graham a5285306c1 Simplify queueEntryPoints() with isMethodRenamable() 4 years ago
Graham 5355abf4a1 Change "dummy" to "unused" in log output for consistency 4 years ago
Graham 4ec4ef84c2 Rename unpacker.jar to unpackclass.jar 4 years ago
Graham 962b494a86 Rename DummyLocalTransformer to UnusedLocalTransformer 4 years ago
Graham ff594848d5 Replace DummyArgTransformer with new ConstantArgTransformer 4 years ago
Graham 0626fd5133 Rename ArgRef::arg to index 4 years ago
Graham 286be9cdd3 Use work lists to speed up DataFlowAnalyzer 4 years ago
Graham 95c5ad0ffe Remove redundant generic type parameter 4 years ago
Graham 09f17d246b Add additional removeFirst() variants to IterableUtils 4 years ago
Graham 5d8dce0ed2 Switch from Guava graphs to JGraphT 4 years ago
Graham e3095f4a7e Use fastutil to reduce allocations in LiveVariableAnalyzer 4 years ago
Graham 6c31bf9af7 Update dependencies 4 years ago
Graham 036ded1ded Use informational colour for the ISC license badge 4 years ago
Graham 3dab5410b1 Add references for badge images/links 4 years ago
Graham 95fb3ceeb0 Change alt text of the build badge to 'Drone' 4 years ago
Graham c29e4617d4 Add Discord and license badges 4 years ago
Graham 70ae010e5a Fix modewhat/where mix up in the first client parameters paragraph 4 years ago
Graham 7415ae6ec9 Run tests with Gradle instead of IDEA's test runner 4 years ago
Graham a553a08def Mark additional methods and classes as final where possible 4 years ago
Graham 01b83b666e Make classes generated by StaticScramblingTransformer final 4 years ago
Graham 96cdcad2af Use tasks extension methods where possible 4 years ago
Graham 11c3b376e2 Set wrapper task's default distributionType to ALL 4 years ago
Graham f14fb762df Simplify InvokeSpecialTransformer 4 years ago
Graham 7d985217ab Add blank line to LiveVariableAnalyzer for readability 4 years ago
Graham 00a85eaa81 Update dependencies 4 years ago
Graham 231e0893af Add java.nio.file.Paths import 4 years ago
Graham 4a40b2cc68 Use NoOpCliktCommand to replace the empty run methods 4 years ago
Graham c9a2b14dfa Fix removal of empty classes only referenced by other empty classes 4 years ago
Graham 81bbc71e29 Allow prePass and postPass to force another pass 4 years ago
Graham 3ed591d89b Reduce size of the use block in Crc32Command 4 years ago
Graham 465078256f Rename common module to util again 4 years ago
Graham 177326221f Move cryptographic code to a separate module 4 years ago
Graham eae715231b Move subcommands to init block for consistency 4 years ago
Graham c20dad7a5e Fix DeobfuscateAstCommand class name 4 years ago
Graham 9d6c8f8735 Add stdin support to the Gradle :run tasks 4 years ago
Graham 44fb2ebe25 Add crc32 command 4 years ago
Graham c849c5ad46 Add command-line tools for headerless compression 4 years ago
Graham 892d21df10 Add headerless bzip2 and gzip library 4 years ago
Graham a52d58e8b5 Use Clikt to implement the command-line interface 4 years ago
Graham 1552b53ace Update to ASM 8.0 4 years ago
Graham 2862b305c5 Convert Java 1.1-style synchronized blocks to a more modern style 4 years ago
Graham 3b73358183 Improve ClassLiteralTransformer 4 years ago
Graham d414f73c20 Move DCO to a separate file 4 years ago
Graham e9df70e7e8 Fix license file name in all/build.gradle.kts 4 years ago
Graham db02aeb1bb Update dependencies 4 years ago
Graham 061e01d57f Rename game_unpacker.dat to unpackclass.pack for consistency 4 years ago
Graham 5daf16a4e9 Update Gradle 4 years ago
Graham 4a7a5f28b1 Add PARAMETER ElementType to Pc annotation 4 years ago
Graham 462ae7cb9c Improve PrettyPrinterConfiguration 4 years ago
Graham b9c707782c Place @Pc annotations on the same line as variable declarations 4 years ago
Graham 7650470143 Downgrade Fernflower 4 years ago
Graham d2069fc975 Update dependencies 4 years ago
Graham 0b51e998cf Increase VALIDITY_PERIOD to 10 years 4 years ago
Graham caed5cebce Remove gl-dri from AstDeobfuscator 4 years ago
Graham dae73b667a Replace !Optional.isPresent() with Optional.isEmpty() 4 years ago
Graham d4da4e2de3 Replace SystemClassLoader with the PlatformClassLoader 4 years ago
Graham 8e0b480cfb Replace jarsigner with the jdk.security.jarsigner API 4 years ago
Graham 164e02cbce Switch to JDK11 4 years ago
Graham a096eaf288 Remove spaces from brace expansion in .editorconfig 4 years ago
Graham 036c3b5a9d Remove gl-dri from settings.gradle.kts 4 years ago
Graham efa9c90f57 Replace shelling out to keytool with Bouncy Castle and the security API 4 years ago
Graham cf132cb2dd Rename KEY_LENGTH to CLIENT_KEY_LENGTH 4 years ago
Graham 2fa55b3ce1 Add length parameter to Rsa.generateKeyPair() 4 years ago
Graham 6326aa93db Combine the deobfuscator's jaggl and jaggl_dri outputs 4 years ago
Graham f5a0680348 Format .editorconfig 4 years ago
Graham 373fdc24b4 Update dependencies 4 years ago
Graham 14301b4820 Document comment code style in the README 4 years ago
Graham 2e1c6c22ff Rename GlConstantTransformer to GlTransformer 4 years ago
Graham c9a3580e11 Allow GlRegistry to be overriden 4 years ago
Graham ace76fe9e4 Transform GL_FRAMEBUFFER_COMPLETE literals 4 years ago
Graham 9db5025c41 Output hex literals in upper case 4 years ago
Graham 59a78bea39 Fix GL_POINT_SIZE_{MIN,MAX} groups 4 years ago
Graham 784ad9900e Always use GL_ constants without vendor suffixes where possible 4 years ago
Graham a704cf28ef Add missing PixelType groups for glDrawPixels() calls 4 years ago
Graham 4fd330a8ac Transform OpenGL constants inside binary/ternary expressions too 4 years ago
Graham e06e70bc1f Rename gl* method argument names 4 years ago
Major 7b92ccb244 Add RedundantGotoTransformer 4 years ago
Major dc36c7835c Throw exception on invalid try-catch nodes 4 years ago
Graham 59159f17a3 Exclude pull requests to master from the deploy stage 4 years ago
Graham 161ff2cea8 Simplify regex in ExceptionTracingTransformer 4 years ago
Graham 6cc9ab9e3b Exclude virtual nodes from the original PC counter 4 years ago
Graham a027209b18 Update ciManagement 4 years ago
Graham aea30dd3f1 Remove Jenkinsfile from .editorconfig 4 years ago
Graham f19aed20f0 Add YAML style to .editorconfig 4 years ago
Graham 1e13174cc2 Switch from Jenkins to Drone 4 years ago
Major 7996d327ff Fix dummy arg removal for virtual methods 4 years ago
Graham 51890c56b3 Track original constructor names and arguments too 4 years ago
Major 488e8ef8c3 Add UnusedMethodTransformer 4 years ago
Graham 9617cc4326 Document interface member visibility logic 4 years ago
Major a2eeecd6f6 Run FinalTransformer later 4 years ago
Major a869d47d11 Add Fernflower Exception transformer 4 years ago
Graham e7c3afab19 Prevent non-static fields from overriding other fields 4 years ago
Graham e6461176b7 De-duplicate inherited member set creation 4 years ago
Graham d57cf15bd3 Add superClassAndInterfaces method to reduce duplication 4 years ago
Graham 5103eb15a4 Fix static member overrides in populateInherited{Field,Method}Sets 4 years ago
Graham 8cac7a48b5 Add getFieldAccess 4 years ago
Graham 8b5f3402e2 Rename getAccess to getMethodAccess 4 years ago
Graham 626202b451 Move isMethodRenamable below createMethodMapping for consistency 4 years ago
Graham 6be3f915f0 Add generate{Class,Field}Name methods 4 years ago
Graham c4ea6cf150 Group isClassRenamable with populateClassMapping 4 years ago
Graham b0dddff729 Add isFieldRenamable method 4 years ago
Graham 5c1768e824 Improve consistency of isClassRenamable and isMethodImmutable 4 years ago
Graham f4933a2b59 Add support for translating gl calls on the jaggl.opengl object 4 years ago
Graham 0564bc29b0 Add missing ProgramPropertyARB groups 4 years ago
Graham 133e2fac77 Set glGetObjectParameterivARB's pname group to ProgramPropertyARB 4 years ago
Graham 530af7628b Add EnableCap group to GL_TEXTURE_3D 4 years ago
Graham 1a2f0bd9e5 Add MaterialFace group to GL_FRONT, GL_BACK and GL_FRONT_AND_BACK 4 years ago
Graham aaaa0c0c68 Fix appending fields to javax.media.opengl.GL 4 years ago
Graham ae9dccf9dd Add CheckedInt32 group to most enums 4 years ago
Graham 7e66c024b4 Propagate groups from <enums> to each individual <enum> 4 years ago
Major 9a1599880b Don't rename classes containing native methods 4 years ago
Major f04f4f9685 Update tracing exception pattern 4 years ago
Graham dd8dc533e9 Set the visibility of interface members to public 4 years ago
Graham 78db7d3179 Account for inheritance in StaticScramblingTransformer 4 years ago
Major a9312f3cd2 Remove empty try-catch blocks in DCE 4 years ago
Graham 9b5d4221b1 Fix GL_COORD_REPLACE group 4 years ago
Major 5fa9e9a621 Remove spurious toCollection calls 4 years ago
Graham 3a29f61d7c Remove existing FrameNode manipulation support 4 years ago
Graham 43646bbd9d Format TypedRemapper 4 years ago
Graham e6402d52c2 Add StackFrameClassWriter 4 years ago
Graham 1805873385 Changed 'Signed-off-by:' to 'Signed-off-by: line' 4 years ago
Graham ea336582d9 Improve README text slightly 4 years ago
Major 097b334646 Propagate null frame types when remapping 4 years ago
Graham 077fda4bab Remove blank line for consistency 4 years ago
Graham 8ca3f428d2 Remove field.value null check 4 years ago
Graham 00f0279e15 Use safe call operator 4 years ago
Graham a54c540290 Document how to add Signed-off-by to all commits automatically 4 years ago
Major 5e32296905 Fix NPE for FrameNodes that aren't NEW/FULL 4 years ago
Major b391191b75 Add error messages to AsmClassMetadata 4 years ago
Graham 6f7d9be26c Add Pc annotation 4 years ago
Graham be14ffdc2d Add custom Attribute for tracking original instruction indexes 4 years ago
Graham 6464f8dc77 Remap ClassNodes directly 4 years ago
Graham d32de8537d Prefer local Maven repository over the OpenRS2 snapshots repo 4 years ago
Graham 018e0eb9cb Fix reference in README 4 years ago
Major 45bf24c51d Remove copies in InsnMatcher 4 years ago
Graham 1d29aa8c50 Make the native libraries FAQ section make sense again 4 years ago
Major 424a2d7a31 Fix compilation errors 4 years ago
Major 34507a92db Use unix configuration in jimfs tests 4 years ago
Graham 282dbd268b Remove repository credentials from the repositories block 4 years ago
Graham b38da58bd5 Add missing groups to the OpenGL registry 4 years ago
Graham c16b8b30af Replace *toTypedArray() with plus operator 4 years ago
Graham 7db0c971ee Use OpenGL registry to replace magic numbers with constants 4 years ago
Graham 597aa2018e Add support for whole program AST transforms 4 years ago
Graham a777ae362e Add underscore separator to field names 4 years ago
Graham 446dc00552 Update Gradle 4 years ago
Graham 0897991f4e Update openrs2-natives-all 4 years ago
Graham 6e5a58493e Use chat.openrs2.dev in case the Discord link ever changes 4 years ago
Graham a9ce6a19df Emphasise that Jagex's IP should not be included in this repository 4 years ago
Graham bb6bd3861e Switch to the ISC license 4 years ago
Graham a57b6dcc9b Add FAQ item explaining why we run our own dev infrastructure 4 years ago
Graham e0e2c94ff5 Add website link to the README 4 years ago
Graham 6cc29c86ac Clarify that the JDK is required at runtime 4 years ago
Graham 7b5c321def Suggest discussing changes on Discord before starting contributions 4 years ago
Graham a2ff491ae4 Add community section to the README 4 years ago
Graham 9e3299657e Add link to the Git rewriting history page 4 years ago
Graham 103ca48f42 Remove unused Maven reference from the README 4 years ago
Graham 4bda47e628 Update dependencies 4 years ago
Graham 84ade956af Sort static fields above instance fields 4 years ago
Graham ad53f9a78d Sort methods 4 years ago
Graham 09bdf640f4 Add openrs2 prefix to repo{Username,Password} properties 4 years ago
Graham 9cbe8b5b28 Simplify excluding the opaque predicate when removing the initializer 4 years ago
Graham 70d46e7f64 Sort imports 4 years ago
Graham 16d16b8efa Reduce maximum size of each static class 4 years ago
Graham e26778ffb0 Remove unused empty classes 4 years ago
Graham b6bba95435 Simplify static field scrambling 4 years ago
Graham c70d810057 Don't move static fields with to complex initializers 4 years ago
Graham 64ba68bac9 Move static fields without initializers 4 years ago
Graham 84c37f4300 Add logging to StaticScramblingTransformer 4 years ago
Graham 9089fe834a Add initial static field scrambling support 4 years ago
Graham ce74765269 Add static method scrambling transformer 4 years ago
Graham 4468766a9d Ensure ACC_SUPER is set in InvokeSpecialTransformer 4 years ago
Graham ccb37f120b Add INVOKESPECIAL transformer 4 years ago
Graham a82d2e3cef Transform class literals to Java 5 format 4 years ago
Graham bfcb373ade Add ClassVersionUtils 4 years ago
Graham 5c5a908a37 Set decompiler's heap size to 3 GiB 4 years ago
Graham 752941f9f3 Add unpackclass to DEFAULT_PUBLIC_CTOR_CLASSES 4 years ago
Graham 626cbacc06 Remove colons from debug log messages for consistency 4 years ago
Graham 1b1bb7dc5b Change 'user interface' to 'game frame' in the FAQ 4 years ago
Graham caf7efdd6c Use weakest possible visibility for each method/field 4 years ago
Graham fda857963b Make ClassPath::get() return null if a class is not found 4 years ago
Graham 59ac765ef8 Add method for finding classes loaded with Class.forName() 4 years ago
Graham 34ef4f9a5e Rename ClassForNameRemapper to ClassForNameUtils 4 years ago
Graham cad451d3e1 Make ClassForNameRemapper compatible with packaged classes 4 years ago
Graham 3c7989455b Add ClassNameExtensions 4 years ago
Graham c7f5b0b6b3 Replace isNative() with more generic getAccess() method 4 years ago
Graham deb65474c7 Update dependencies 4 years ago
Graham 98abb15847 Update dependencies 4 years ago
Graham 4536fc58a2 Update Gradle 4 years ago
Graham f30a1f88b0 Update dependencies 4 years ago
Graham 62a34ecadc Update Gradle 4 years ago
Graham a66e3eeb9b Move CounterTransformer later in the pipeline 4 years ago
Graham d817856da1 Remove counters with multiple increments 4 years ago
Graham 3124624b5e Make EXCLUDED_{METHODS,FIELDS} public 4 years ago
Graham 7bb1218b42 Add sequential property to AbstractInsnNode 4 years ago
Graham 25a6953644 Fix use of deprecated JavaParser methods 4 years ago
Graham 0a988584b7 Convert some deob-ast methods to extension methods 4 years ago
Graham 5487a74eb8 Remove traversal argument from walk extension method 4 years ago
Graham 54aec42f90 Format .kts files 4 years ago
Graham 3f2335859d Add support for impure expressions to InsnListUtils 4 years ago
Graham f8acce846b Format .editorconfig file 4 years ago
Graham c0da020649 Fix handling of argumentsAndReturnSizes for static methods 4 years ago
Graham 713009470f Fix NEW StackMetadata 4 years ago
Graham a0d6a48922 Fix StackMetadata for MethodInsnNodes 4 years ago
Graham 64a9ae33cb Fix StackMetadata for FieldInsnNodes 4 years ago
Graham e0d6390f87 Add getSimpleExpression method 4 years ago
Graham 5d813a345d Reduce priority of mavenLocal() to fix dependencyUpdates 4 years ago
Graham 641da067da Depend on kotlin-inline-logger directly 4 years ago
Graham 7708056488 Use kotlin-inline-logger from Maven Central 4 years ago
Graham e6ac51688c Skip openrs2 repositories if no username/password are supplied 4 years ago
Graham e480ce282a Avoid write() calls with a length of zero 4 years ago
Graham 8d90a70b6a Fix SkipOutputStream 4 years ago
Graham f96e9225bf Update dependencies 4 years ago
Graham 4e75f15e93 Add Jenkinsfile to .editorconfig 4 years ago
Graham 73df826e85 Add Jenkinsfile 4 years ago
Graham a1d1c74987 Enable strict JSR-305 mode 4 years ago
Graham ccbbf873af Switch from Maven to Gradle 4 years ago
Graham 785ce2d9ca Use require() in PlatformDetectionTransformer 4 years ago
Graham 8df0907118 Replace IllegalStateException with error() in BitMaskTransformer 4 years ago
Graham 248cad99e3 Improve safety of IFEQ/IFNE check in OpaquePredicateTransformer 4 years ago
Graham c4e08242aa Fix handling of opaque predicate embedded in flow obstructor initializer 4 years ago
Graham 323d23b6d4 Remove spelling inspection 4 years ago
Graham abf803f017 Replace contains() calls with the in operator 4 years ago
Graham 701b162dec Remove redundant public modifier 4 years ago
Graham de826927aa Update dependencies 4 years ago
Graham 64abe46971 Replace forEach calls with for loops 4 years ago
Graham 4314aededd Simplify hasSideEffects() calls in AddSubTransformer 4 years ago
Graham 784b14b66b Improve safety of ForLoopConditionTransformer 4 years ago
Graham b611854dbc Document client parameters 4 years ago
Graham a4cd83fff0 Fix typo in the Portuguese translation of 'loading textures' 4 years ago
Graham 37abac9e48 Disable ASCII_STRING_CHARACTERS option 4 years ago
Graham f1ed531077 Update Fernflower 4 years ago
Graham ab75a2f0c9 Add IdentityTransformer 4 years ago
Graham 8f3591670b Use elvis operator in readJar() 4 years ago
Graham 2e1db9f0b1 Don't swap for loop conditions where both sides have a side effect 4 years ago
Desetude d8c3f4e31c Flip > and >= in for loop conditions to < and <= respectively 4 years ago
Graham cac3191a4a Update dependencies 4 years ago
Graham d575c25ebd Switch license to GPLv3 or later 4 years ago
Graham dedb502df4 Add Developer Certificate of Origin to the README 4 years ago
  1. 29
      .editorconfig
  2. 34
      .github/workflows/build.yaml
  3. 6
      .gitignore
  4. 1
      .idea/.gitignore
  5. 28
      .idea/codeInsightSettings.xml
  6. 14
      .idea/codeStyles/Project.xml
  7. 6
      .idea/fileTemplates/internal/Kotlin Class.kt
  8. 6
      .idea/fileTemplates/internal/Kotlin Enum.kt
  9. 6
      .idea/fileTemplates/internal/Kotlin Interface.kt
  10. 5
      .idea/inspectionProfiles/Project_Default.xml
  11. 13
      .idea/runConfigurations/AstDeobfuscator.xml
  12. 15
      .idea/runConfigurations/Bundler.xml
  13. 10
      .idea/runConfigurations/BytecodeDeobfuscator.xml
  14. 14
      .idea/runConfigurations/Decompiler.xml
  15. 14
      .idea/runConfigurations/Deobfuscator.xml
  16. 12
      .idea/runConfigurations/GameServer.xml
  17. 10
      .idea/runConfigurations/GenerateBuffer.xml
  18. 10
      .idea/runConfigurations/Patcher.xml
  19. 16
      .idea/runConfigurations/XteaPluginTest.xml
  20. 4
      .idea/runConfigurations/client.xml
  21. 12
      .idea/runConfigurations/org_openrs2.xml
  22. 2
      .idea/scopes/exclude_nonfree.xml
  23. 2
      .mailmap
  24. 80
      CONTRIBUTING.md
  25. 674
      COPYING
  26. 37
      DCO
  27. 13
      LICENSE
  28. 140
      README.md
  29. 125
      all/build.gradle.kts
  30. 90
      all/pom.xml
  31. 30
      all/src/assembly/bin.xml
  32. 3
      all/src/bin/openrs2-bundle
  33. 3
      all/src/bin/openrs2-bundle.cmd
  34. 3
      all/src/bin/openrs2-decompiler
  35. 3
      all/src/bin/openrs2-decompiler.cmd
  36. 3
      all/src/bin/openrs2-deob
  37. 3
      all/src/bin/openrs2-deob-ast
  38. 3
      all/src/bin/openrs2-deob-ast.cmd
  39. 3
      all/src/bin/openrs2-deob.cmd
  40. 3
      all/src/bin/openrs2-game
  41. 3
      all/src/bin/openrs2-game.cmd
  42. 29
      all/src/main/kotlin/org/openrs2/Command.kt
  43. 64
      archive/build.gradle.kts
  44. 23
      archive/src/main/kotlin/org/openrs2/archive/ArchiveCommand.kt
  45. 3
      archive/src/main/kotlin/org/openrs2/archive/ArchiveConfig.kt
  46. 27
      archive/src/main/kotlin/org/openrs2/archive/ArchiveConfigProvider.kt
  47. 55
      archive/src/main/kotlin/org/openrs2/archive/ArchiveModule.kt
  48. 28
      archive/src/main/kotlin/org/openrs2/archive/DataSourceProvider.kt
  49. 15
      archive/src/main/kotlin/org/openrs2/archive/DatabaseProvider.kt
  50. 19
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheCommand.kt
  51. 158
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheDownloader.kt
  52. 806
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt
  53. 1490
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheImporter.kt
  54. 16
      archive/src/main/kotlin/org/openrs2/archive/cache/CrossPollinateCommand.kt
  55. 223
      archive/src/main/kotlin/org/openrs2/archive/cache/CrossPollinator.kt
  56. 25
      archive/src/main/kotlin/org/openrs2/archive/cache/DownloadCommand.kt
  57. 34
      archive/src/main/kotlin/org/openrs2/archive/cache/ExportCommand.kt
  58. 53
      archive/src/main/kotlin/org/openrs2/archive/cache/ImportCommand.kt
  59. 116
      archive/src/main/kotlin/org/openrs2/archive/cache/ImportMasterIndexCommand.kt
  60. 376
      archive/src/main/kotlin/org/openrs2/archive/cache/Js5ChannelHandler.kt
  61. 158
      archive/src/main/kotlin/org/openrs2/archive/cache/NxtJs5ChannelHandler.kt
  62. 22
      archive/src/main/kotlin/org/openrs2/archive/cache/NxtJs5ChannelInitializer.kt
  63. 76
      archive/src/main/kotlin/org/openrs2/archive/cache/OsrsJs5ChannelHandler.kt
  64. 22
      archive/src/main/kotlin/org/openrs2/archive/cache/OsrsJs5ChannelInitializer.kt
  65. 16
      archive/src/main/kotlin/org/openrs2/archive/cache/RefreshViewsCommand.kt
  66. 149
      archive/src/main/kotlin/org/openrs2/archive/cache/finder/CacheFinderExtractor.kt
  67. 25
      archive/src/main/kotlin/org/openrs2/archive/cache/finder/ExtractCommand.kt
  68. 8
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/ClientOutOfDateCodec.kt
  69. 10
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/InitJs5RemoteConnection.kt
  70. 27
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/InitJs5RemoteConnectionCodec.kt
  71. 8
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/Js5OkCodec.kt
  72. 14
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/Js5Request.kt
  73. 37
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/Js5RequestEncoder.kt
  74. 11
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/Js5Response.kt
  75. 121
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/Js5ResponseDecoder.kt
  76. 8
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/LoginResponse.kt
  77. 32
      archive/src/main/kotlin/org/openrs2/archive/cache/nxt/MusicStreamClient.kt
  78. 11
      archive/src/main/kotlin/org/openrs2/archive/client/Architecture.kt
  79. 35
      archive/src/main/kotlin/org/openrs2/archive/client/Artifact.kt
  80. 46
      archive/src/main/kotlin/org/openrs2/archive/client/ArtifactFormat.kt
  81. 16
      archive/src/main/kotlin/org/openrs2/archive/client/ArtifactType.kt
  82. 14
      archive/src/main/kotlin/org/openrs2/archive/client/ClientCommand.kt
  83. 455
      archive/src/main/kotlin/org/openrs2/archive/client/ClientExporter.kt
  84. 997
      archive/src/main/kotlin/org/openrs2/archive/client/ClientImporter.kt
  85. 30
      archive/src/main/kotlin/org/openrs2/archive/client/ExportCommand.kt
  86. 32
      archive/src/main/kotlin/org/openrs2/archive/client/ImportCommand.kt
  87. 7
      archive/src/main/kotlin/org/openrs2/archive/client/Jvm.kt
  88. 116
      archive/src/main/kotlin/org/openrs2/archive/client/MachO.kt
  89. 43
      archive/src/main/kotlin/org/openrs2/archive/client/OperatingSystem.kt
  90. 16
      archive/src/main/kotlin/org/openrs2/archive/client/RefreshCommand.kt
  91. 11
      archive/src/main/kotlin/org/openrs2/archive/game/Game.kt
  92. 58
      archive/src/main/kotlin/org/openrs2/archive/game/GameDatabase.kt
  93. 70
      archive/src/main/kotlin/org/openrs2/archive/jav/JavConfig.kt
  94. 65
      archive/src/main/kotlin/org/openrs2/archive/key/BinaryKeyReader.kt
  95. 16
      archive/src/main/kotlin/org/openrs2/archive/key/BruteForceCommand.kt
  96. 16
      archive/src/main/kotlin/org/openrs2/archive/key/DownloadCommand.kt
  97. 16
      archive/src/main/kotlin/org/openrs2/archive/key/EntCommand.kt
  98. 57
      archive/src/main/kotlin/org/openrs2/archive/key/HdosKeyDownloader.kt
  99. 13
      archive/src/main/kotlin/org/openrs2/archive/key/HexKeyReader.kt
  100. 23
      archive/src/main/kotlin/org/openrs2/archive/key/ImportCommand.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -7,13 +7,34 @@ trim_trailing_whitespace = true
max_line_length = 120
indent_style = tab
[*.kt]
# Prevent IDEA from translating *.{kt,kts} -> *.{kt, kts}. This confuses ktlint.
# @formatter:off
[*.{kt,kts}]
# @formatter:on
indent_style = space
indent_size = 4
# see https://github.com/pinterest/ktlint/issues/527
# noinspection EditorConfigKeyCorrectness
disabled_rules = import-ordering
ktlint_standard_argument-list-wrapping = disabled
# noinspection EditorConfigKeyCorrectness
ktlint_standard_indent = disabled
# noinspection EditorConfigKeyCorrectness
ktlint_standard_parameter-list-wrapping = disabled
# noinspection EditorConfigKeyCorrectness
ktlint_standard_trailing-comma-on-call-site = disabled
# noinspection EditorConfigKeyCorrectness
ktlint_standard_trailing-comma-on-declaration-site = disabled
# noinspection EditorConfigKeyCorrectness
ktlint_standard_wrapping = disabled
[*.md]
max_line_length = 80
[*.sql]
indent_style = space
indent_size = 4
[*.xml]
# @formatter:off
[*.{json,xml,yaml,yml}]
# @formatter:on
indent_style = space
indent_size = 2

@ -0,0 +1,34 @@
---
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/cache@v3
with:
path: ~/.ssh/known_hosts
key: ssh-known-hosts
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/gradle-build-action@v2
with:
arguments: build
- if: github.ref == 'refs/heads/master'
run: |
install -dm0700 ~/.ssh
touch ~/.ssh/id_ed25519
chmod 0600 ~/.ssh/id_ed25519
echo "${SSH_KEY}" > ~/.ssh/id_ed25519
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
- if: github.ref == 'refs/heads/master'
uses: gradle/gradle-build-action@v2
with:
arguments: publish
env:
ORG_GRADLE_PROJECT_openrs2Username: ${{ secrets.REPO_USERNAME }}
ORG_GRADLE_PROJECT_openrs2Password: ${{ secrets.REPO_PASSWORD }}

6
.gitignore vendored

@ -4,10 +4,12 @@
!.idea
!.mailmap
*.iml
*/build
*/out
*/target
*~
/nonfree
/build
/out
/target
hs_err_pid*.log
pom.xml.releaseBackup
release.properties

1
.idea/.gitignore vendored

@ -1,5 +1,6 @@
/*
!.gitignore
!codeInsightSettings.xml
!codeStyles
!fileTemplates
!inspectionProfiles

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaProjectCodeInsightSettings">
<excluded-names>
<name>com.google.inject.BindingAnnotation</name>
<name>com.google.inject.Inject</name>
<name>com.google.inject.Named</name>
<name>com.google.inject.Provider</name>
<name>com.google.inject.ScopeAnnotation</name>
<name>com.google.inject.Singleton</name>
<name>java.nio.file.Paths.get</name>
<name>org.junit.jupiter.api.AfterEach</name>
<name>org.junit.jupiter.api.Assertions.assertEquals</name>
<name>org.junit.jupiter.api.Assertions.assertFalse</name>
<name>org.junit.jupiter.api.Assertions.assertNotEquals</name>
<name>org.junit.jupiter.api.Assertions.assertNotNull</name>
<name>org.junit.jupiter.api.Assertions.assertNotSame</name>
<name>org.junit.jupiter.api.Assertions.assertNull</name>
<name>org.junit.jupiter.api.Assertions.assertSame</name>
<name>org.junit.jupiter.api.Assertions.assertThrows</name>
<name>org.junit.jupiter.api.Assertions.assertTrue</name>
<name>org.junit.jupiter.api.Assertions.fail</name>
<name>org.junit.jupiter.api.BeforeEach</name>
<name>org.junit.jupiter.api.Disabled</name>
<name>org.junit.jupiter.api.Test</name>
</excluded-names>
</component>
</project>

@ -7,6 +7,10 @@
</value>
</option>
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
</HTMLCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="CLASS_NAMES_IN_JAVADOC" value="3" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
@ -43,6 +47,13 @@
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="HTML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
@ -70,6 +81,9 @@
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Markdown">
<option name="RIGHT_MARGIN" value="80" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="INDENT_SIZE" value="2" />

@ -0,0 +1,6 @@
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
#parse("File Header.java")
public class ${NAME} {
}

@ -0,0 +1,6 @@
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
#parse("File Header.java")
public enum class ${NAME} {
}

@ -0,0 +1,6 @@
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
#end
#parse("File Header.java")
public interface ${NAME} {
}

@ -1,6 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
<option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" />

@ -1,13 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="AstDeobfuscator" type="JetRunConfigurationType" factoryName="Kotlin">
<module name="openrs2-deob-ast" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="dev.openrs2.deob.ast.AstDeobfuscatorKt" />
<option name="WORKING_DIRECTORY" value="" />
<configuration default="false" name="AstDeobfuscator" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.deob.ast.DeobfuscateAstCommandKt" />
<module name="openrs2.deob-ast.main" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Bundler" type="JetRunConfigurationType" factoryName="Kotlin">
<module name="openrs2-bundler" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="dev.openrs2.bundler.BundlerKt" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BytecodeDeobfuscator" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.deob.bytecode.DeobfuscateBytecodeCommandKt" />
<module name="openrs2.deob-bytecode.main" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

@ -1,13 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Decompiler" type="JetRunConfigurationType" factoryName="Kotlin">
<module name="openrs2-decompiler" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="dev.openrs2.decompiler.DecompilerKt" />
<option name="WORKING_DIRECTORY" value="" />
<configuration default="false" name="Decompiler" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.decompiler.DecompileCommandKt" />
<module name="openrs2.decompiler.main" />
<shortenClasspath name="NONE" />
<option name="VM_PARAMETERS" value="-Xmx3G" />
<method v="2">
<option name="Make" enabled="true" />
</method>

@ -1,13 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deobfuscator" type="JetRunConfigurationType" factoryName="Kotlin">
<module name="openrs2-deob" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="dev.openrs2.deob.DeobfuscatorKt" />
<option name="WORKING_DIRECTORY" value="" />
<configuration default="false" name="Deobfuscator" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.deob.DeobfuscateCommandKt" />
<module name="openrs2.deob.main" />
<shortenClasspath name="NONE" />
<option name="VM_PARAMETERS" value="-Xmx3G" />
<method v="2">
<option name="Make" enabled="true" />
</method>

@ -1,13 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="GameServer" type="JetRunConfigurationType" factoryName="Kotlin">
<module name="openrs2-game" />
<configuration default="false" name="GameServer" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.game.GameCommandKt" />
<module name="openrs2.game.main" />
<shortenClasspath name="NONE" />
<option name="VM_PARAMETERS" value="-Dio.netty.leakDetection.level=PARANOID" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="dev.openrs2.game.GameServerKt" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Make" enabled="true" />
</method>

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="GenerateBuffer" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.buffer.generator.GenerateBufferCommandKt" />
<module name="openrs2.buffer-generator.main" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Patcher" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="org.openrs2.patcher.PatchCommandKt" />
<module name="openrs2.patcher.main" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="XteaPluginTest" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="org.openrs2.xtea.XteaPluginTest" />
<module name="openrs2.xtea-plugin.test" />
<option name="VM_PARAMETERS" value="-ea" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.openrs2.xtea.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="client" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="client" />
<module name="openrs2-client" />
<option name="PROGRAM_PARAMETERS" value="1 live english game0" />
<module name="openrs2.nonfree.client.main" />
<option name="PROGRAM_PARAMETERS" value="1 live en game0" />
<method v="2">
<option name="Make" enabled="true" />
</method>

@ -1,11 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dev.openrs2" type="JUnit" factoryName="JUnit">
<option name="PACKAGE_NAME" value="dev.openrs2" />
<configuration default="false" name="org.openrs2" type="JUnit" factoryName="JUnit">
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.openrs2.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.openrs2" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -Dio.netty.leakDetection.level=PARANOID" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" />
</option>

@ -1,3 +1,3 @@
<component name="DependencyValidationManager">
<scope name="exclude-nonfree" pattern="!file[openrs2-client]:*/&amp;&amp;!file[openrs2-gl]:*/&amp;&amp;!file[openrs2-gl-dri]:*/&amp;&amp;!file[openrs2-loader]:*/&amp;&amp;!file[openrs2-nonfree]:*/&amp;&amp;!file[openrs2-signlink]:*/&amp;&amp;!file[openrs2-unpack]:*/&amp;&amp;!file[openrs2-unpacker]:*/" />
<scope name="exclude-nonfree" pattern="!file[openrs2.nonfree*]:*//*" />
</component>

@ -0,0 +1,2 @@
Graham <gpe@openrs2.org> <gpe@openrs2.dev>
Scu11 <scu11@openrs2.org> <scu11@openrs2.dev>

@ -0,0 +1,80 @@
# Contributing to OpenRS2
## Introduction
OpenRS2 is still in the early stages of development. The current focus is on
building underlying infrastructure, such as the deobfuscator, rather than game
content. This approach will make it much quicker to build game content in the
long run, but it does mean OpenRS2 won't be particularly useful in the short
term.
If you're interested in contributing new features, you should discuss your plans
in our [Discord][discord] server first. I have rough plans in my head for the
future development direction. Communicating beforehand will avoid the need for
significant changes to be made at the code review stage and make it less likely
for your contribution to be dropped entirely.
## Code style
All source code must be formatted with [IntelliJ IDEA][idea]'s built-in
formatter before each commit. The 'Optimize imports' option should also be
selected. Do not select 'Rearrange entries'.
OpenRS2's code style settings are held in `.idea/codeStyles/Project.xml` in the
repository, and IDEA should use them automatically after importing the Gradle
project.
Kotlin code must pass all of [ktlint][ktlint]'s tests.
Always use `//` for single-line comments and `/*` for multi-line comments.
## Commit messages
Commit messages should follow the ['seven rules'][commitmsg] described in
'How to Write a Git Commit Message', with the exception that the summary line
can be up to 72 characters in length (as OpenRS2 does not use email-based
patches).
You should use tools like [interactive rebase][rewriting-history] to ensure the
commit history is tidy.
## Developer Certificate of Origin
OpenRS2 uses version 1.1 of the [Developer Certificate of Origin][dco] (DCO) to
certify that contributors agree to license their code under OpenRS2's license
(see the License section below). To confirm that a contribution meets the
requirements of the DCO, a `Signed-off-by:` line must be added to the Git commit
message by passing `--signoff` to the `git commit` invocation.
If you intend to make a large number of contributions, run the following
commands from the repository root to add `Signed-off-by:` line to all your
commit messages by default:
```
echo -e "\n\nSigned-off-by: $(git config user.name) <$(git config user.email)>" > .git/commit-template
git config commit.template .git/commit-template
```
The full text of the DCO is available in the `DCO` file.
OpenRS2 does not distribute any of Jagex's intellectual property in this
repository, and care should be taken to avoid inadvertently including any in
contributions.
## Versioning
OpenRS2 uses [Semantic Versioning][semver].
## Gitea
OpenRS2 only uses GitHub as a mirror. Issues and pull requests should be
submitted to [OpenRS2's self-hosted Gitea instance][gitea].
[commitmsg]: https://chris.beams.io/posts/git-commit/#seven-rules
[dco]: https://developercertificate.org/
[discord]: https://chat.openrs2.org/
[gitea]: https://git.openrs2.org/openrs2/openrs2
[idea]: https://www.jetbrains.com/idea/
[ktlint]: https://github.com/pinterest/ktlint#readme
[rewriting-history]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History
[semver]: https://semver.org/

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

37
DCO

@ -0,0 +1,37 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

@ -0,0 +1,13 @@
Copyright (c) 2019-2023 OpenRS2 Authors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

@ -1,114 +1,88 @@
# OpenRS2
[![Build status badge](https://build.openrs2.dev/buildStatus/icon?job=openrs2&build=lastCompleted)](https://build.openrs2.dev/job/openrs2/)
[![GitHub Actions][actions-badge]][actions] [![Discord][discord-badge]][discord] [![ISC license][isc-badge]][isc]
## Introduction
OpenRS2 is an open-source multiplayer game server and suite of associated
tools. It is compatible with build 550 of the RuneScape client, which was
released in late 2009.
OpenRS2 is an open-source multiplayer game server and suite of associated tools.
It is compatible with build 550 of the RuneScape client, which was released in
late 2009.
## Prerequisites
Building OpenRS2 requires the following pieces of software:
OpenRS2 requires version 11 or later of the [Java Development Kit][jdk].
* [Java Development Kit][jdk] (version 8 or later)
* [Apache Maven][maven] (version 3.3.9 or later)
The JDK is required even if a pre-built copy of OpenRS2 is used, as it depends
on JDK-only tools, such as `jarsigner`, at runtime.
### Non-free components
OpenRS2 requires the original RuneScape client code, data and location file
encryption keys, which we cannot legally distribute.
These files must be manually placed in the `nonfree` directory (directly
beneath the root of the repository), in the following structure:
These files must be manually placed in the `nonfree` directory (directly beneath
the root of the repository), in the following structure:
```
nonfree
└── code
   ├── game_unpacker.dat
   ├── jaggl.pack200
   ├── loader_gl.jar
   ├── loader.jar
   ├── runescape_gl.pack200
   └── runescape.jar
├── lib
│ ├── jaggl.pack200
│ ├── loader_gl.jar
│ ├── loader.jar
│ ├── runescape_gl.pack200
│ ├── runescape.jar
│ └── unpackclass.pack
└── share
├── cache
│ ├── 0
│ │ ├── 0.dat
│ │ └── ...
│ ├── ...
│ └── 255
│ ├── ...
│ └── 28.dat
└── keys.json
```
The SHA-256 checksums of the correct files are:
The CRC-32 checksums and SHA-256 digests of the correct files are:
```
7c090e07f8d754d09804ff6e9733ef3ba227893b6b639436db90977b39122590 nonfree/code/game_unpacker.dat
d39578f4a88a376bcb2571f05da1939a14a80d8c4ed89a4eb172d9e525795fe2 nonfree/code/jaggl.pack200
31182683ba04dc0ad45859161c13f66424b10deb0b2df10aa58b48bba57402db nonfree/code/loader_gl.jar
ccdfaa86be07452ddd69f869ade86ea900dbb916fd853db16602edf2eb54211b nonfree/code/loader.jar
4a5032ea8079d2154617ae1f21dfcc46a10e023c8ba23a4827d5e25e75c73045 nonfree/code/runescape_gl.pack200
0ab28a95e7c5993860ff439ebb331c0df02ad40aa1f544777ed91b46d30d3d24 nonfree/code/runescape.jar
```
| CRC-32 checksum | SHA-256 digest | File |
|----------------:|--------------------------------------------------------------------|------------------------|
| `-1418094567` | `d39578f4a88a376bcb2571f05da1939a14a80d8c4ed89a4eb172d9e525795fe2` | `jaggl.pack200` |
| `-2129469231` | `31182683ba04dc0ad45859161c13f66424b10deb0b2df10aa58b48bba57402db` | `loader_gl.jar` |
| `-1516355035` | `ccdfaa86be07452ddd69f869ade86ea900dbb916fd853db16602edf2eb54211b` | `loader.jar` |
| `-132784534` | `4a5032ea8079d2154617ae1f21dfcc46a10e023c8ba23a4827d5e25e75c73045` | `runescape_gl.pack200` |
| `1692522675` | `0ab28a95e7c5993860ff439ebb331c0df02ad40aa1f544777ed91b46d30d3d24` | `runescape.jar` |
| `-1911426584` | `7c090e07f8d754d09804ff6e9733ef3ba227893b6b639436db90977b39122590` | `unpackclass.pack` |
The `nonfree` directory is included in the `.gitignore` file to prevent any
non-free material from being accidentally included in the repository.
The `.gitignore` file includes the `nonfree` directory to prevent any non-free
material from being accidentally included in the repository.
## Building
Run `mvn verify` to download the dependencies, build the code, run the unit
tests and package it.
## Contributing
### Code style
All source code must be formatted with [IntelliJ IDEA][idea]'s built-in
formatter before each commit. The 'Optimize imports' option should also be
selected. Do not select 'Rearrange entries'.
OpenRS2's code style settings are held in `.idea/codeStyles/Project.xml` in the
repository, and IDEA should use them automatically after importing the Maven
project.
Kotlin code must pass all of [ktlint][ktlint]'s tests.
### Commit messages
Run `./gradlew` to download the dependencies, build the code, run the unit tests
and package it.
Commit messages should follow the ['seven rules'][commitmsg] described in
'How to Write a Git Commit Message', with the exception that the summary line
can be up to 72 characters in length (as OpenRS2 does not use email-based
patches).
## Links
### Versioning
OpenRS2 uses [Semantic Versioning][semver].
* [Discord][discord]
* [Issue tracker][issue-tracker]
* [KDoc][kdoc]
* [Website][www]
## License
Unless otherwise stated, all code and data is licensed under version 3.0 (and
only version 3.0) of the [GNU General Public License][gpl]. The full terms are
available in the `COPYING` file.
The `deob-annotations` and `jsobject` modules are instead licensed under
version 3.0 (and only version 3.0) of the
[GNU Lesser General Public License][lgpl]. The full terms are available in the
`COPYING.LESSER` file in each module's directory.
## Copyright
Copyright (c) 2019-2020 OpenRS2 Authors
OpenRS2 is free software: you can redistribute it and/or modify it under the
terms of version 3.0 of the GNU General Public License as published by the Free
Software Foundation.
OpenRS2 is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
OpenRS2. If not, see <https://www.gnu.org/licenses/>.
[commitmsg]: https://chris.beams.io/posts/git-commit/#seven-rules
[gpl]: https://www.gnu.org/licenses/gpl-3.0.html
[idea]: https://www.jetbrains.com/idea/
OpenRS2 is available under the terms of the [ISC license][isc], which is similar
to the 2-clause BSD license. The full copyright notice and terms are available
in the `LICENSE` file.
[actions-badge]: https://github.com/openrs2/openrs2/actions/workflows/build.yaml/badge.svg?branch=master
[actions]: https://github.com/openrs2/openrs2/actions
[discord-badge]: https://img.shields.io/discord/684495254145335298
[discord]: https://chat.openrs2.org/
[isc-badge]: https://img.shields.io/badge/license-ISC-informational
[isc]: https://opensource.org/licenses/ISC
[issue-tracker]: https://git.openrs2.org/openrs2/openrs2/issues
[jdk]: https://jdk.java.net/
[ktlint]: https://github.com/pinterest/ktlint#readme
[lgpl]: https://www.gnu.org/licenses/lgpl-3.0.html
[maven]: https://maven.apache.org/
[semver]: https://semver.org/
[kdoc]: https://docs.openrs2.org/
[www]: https://www.openrs2.org/

@ -0,0 +1,125 @@
import com.github.jk1.license.render.TextReportRenderer
import java.nio.file.Files
plugins {
`maven-publish`
application
alias(libs.plugins.dependencyLicenseReport)
alias(libs.plugins.shadow)
kotlin("jvm")
}
application {
applicationName = "openrs2"
mainClass.set("org.openrs2.CommandKt")
}
dependencies {
implementation(projects.archive)
implementation(projects.bufferGenerator)
implementation(projects.cacheCli)
implementation(projects.compressCli)
implementation(projects.crc32)
implementation(projects.deob)
implementation(projects.game)
implementation(projects.log)
implementation(projects.patcher)
}
tasks.shadowJar {
archiveFileName.set("openrs2.jar")
minimize {
exclude(dependency("ch.qos.logback:logback-classic"))
exclude(dependency("com.github.jnr:jnr-ffi"))
exclude(dependency("org.flywaydb:flyway-core"))
exclude(dependency("org.jetbrains.kotlin:kotlin-reflect"))
}
}
tasks.register("generateAuthors") {
inputs.dir("$rootDir/.git")
outputs.file(layout.buildDirectory.file("AUTHORS"))
doLast {
Files.newOutputStream(layout.buildDirectory.file("AUTHORS").get().asFile.toPath()).use { out ->
exec {
commandLine("git", "shortlog", "-esn", "HEAD")
standardOutput = out
}.assertNormalExitValue()
}
}
}
licenseReport {
renderers = arrayOf(TextReportRenderer())
}
val distTasks = listOf(
"distTar",
"distZip",
"installDist"
)
configure(tasks.filter { it.name in distTasks }) {
enabled = false
}
val shadowDistTasks = listOf(
"installShadowDist",
"shadowDistTar",
"shadowDistZip"
)
configure(tasks.filter { it.name in shadowDistTasks }) {
dependsOn("generateAuthors", "generateLicenseReport")
}
distributions {
named("shadow") {
distributionBaseName.set("openrs2")
contents {
from(layout.buildDirectory.file("AUTHORS"))
from("$rootDir/CONTRIBUTING.md")
from("$rootDir/DCO")
from("$rootDir/LICENSE")
from("$rootDir/README.md")
from("$rootDir/etc/archive.example.yaml") {
rename { "archive.yaml" }
into("etc")
}
from("$rootDir/etc/config.example.yaml") {
rename { "config.yaml" }
into("etc")
}
from("$rootDir/share") {
exclude(".*", "*~")
into("share")
}
from(layout.buildDirectory.file("reports/dependency-license/THIRD-PARTY-NOTICES.txt")) {
rename { "third-party-licenses.txt" }
into("share/doc")
}
}
}
}
publishing {
publications.create<MavenPublication>("maven") {
artifactId = "openrs2"
setArtifacts(listOf(tasks.named("shadowDistZip").get()))
pom {
packaging = "zip"
name.set("OpenRS2")
description.set(
"""
OpenRS2 is an open-source multiplayer game server and suite of
associated tools. It is compatible with build 550 of the
RuneScape client, which was released in mid-2009.
""".trimIndent()
)
}
}
}

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>openrs2-all</artifactId>
<packaging>jar</packaging>
<name>OpenRS2 All</name>
<description>
Zip file for end users, containing OpenRS2, all of its transitive
dependencies and launcher scripts.
</description>
<dependencies>
<dependency>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2-decompiler</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2-deob</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2-deob-ast</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2-game</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>jar-with-dependencies</shadedClassifierName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>dev.openrs2.game.GameServerKt</Main-Class>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptors>
<descriptor>${project.basedir}/src/assembly/bin.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>bin</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}/src/bin</directory>
<outputDirectory>bin</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.parent.basedir}/docs</directory>
<outputDirectory>docs</outputDirectory>
</fileSet>
</fileSets>
<files>
<file>
<source>${project.build.directory}/${project.artifactId}-${project.version}-jar-with-dependencies.jar</source>
<outputDirectory>lib</outputDirectory>
<destName>openrs2.jar</destName>
</file>
<file>
<source>${project.parent.basedir}/COPYING</source>
</file>
<file>
<source>${project.parent.basedir}/README.md</source>
</file>
</files>
</assembly>

@ -1,3 +0,0 @@
#!/bin/sh -e
cd `dirname "$0"`/..
exec java -cp lib/openrs2.jar dev.openrs2.bundler.BundlerKt "$@"

@ -1,3 +0,0 @@
@echo off
cd /d %~dp0\..
java -cp lib\openrs2.jar dev.openrs2.bundler.BundlerKt %*

@ -1,3 +0,0 @@
#!/bin/sh -e
cd `dirname "$0"`/..
exec java -cp lib/openrs2.jar dev.openrs2.decompiler.DecompilerKt "$@"

@ -1,3 +0,0 @@
@echo off
cd /d %~dp0\..
java -cp lib\openrs2.jar dev.openrs2.decompiler.DecompilerKt %*

@ -1,3 +0,0 @@
#!/bin/sh -e
cd `dirname "$0"`/..
exec java -cp lib/openrs2.jar dev.openrs2.deob.DeobfuscatorKt "$@"

@ -1,3 +0,0 @@
#!/bin/sh -e
cd `dirname "$0"`/..
exec java -cp lib/openrs2.jar dev.openrs2.deob.ast.AstDeobfuscatorKt "$@"

@ -1,3 +0,0 @@
@echo off
cd /d %~dp0\..
java -cp lib\openrs2.jar dev.openrs2.deob.ast.AstDeobfuscatorKt %*

@ -1,3 +0,0 @@
@echo off
cd /d %~dp0\..
java -cp lib\openrs2.jar dev.openrs2.deob.DeobfuscatorKt %*

@ -1,3 +0,0 @@
#!/bin/sh -e
cd `dirname "$0"`/..
exec java -cp lib/openrs2.jar dev.openrs2.game.GameServerKt "$@"

@ -1,3 +0,0 @@
@echo off
cd /d %~dp0\..
java -cp lib\openrs2.jar dev.openrs2.game.GameServerKt %*

@ -0,0 +1,29 @@
package org.openrs2
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
import org.openrs2.archive.ArchiveCommand
import org.openrs2.buffer.generator.GenerateBufferCommand
import org.openrs2.cache.cli.CacheCommand
import org.openrs2.compress.cli.CompressCommand
import org.openrs2.crc32.Crc32Command
import org.openrs2.deob.DeobfuscateCommand
import org.openrs2.game.GameCommand
import org.openrs2.patcher.PatchCommand
public fun main(args: Array<String>): Unit = Command().main(args)
public class Command : NoOpCliktCommand(name = "openrs2") {
init {
subcommands(
ArchiveCommand(),
CacheCommand(),
CompressCommand(),
Crc32Command(),
DeobfuscateCommand(),
GameCommand(),
GenerateBufferCommand(),
PatchCommand()
)
}
}

@ -0,0 +1,64 @@
plugins {
`maven-publish`
application
kotlin("jvm")
}
application {
mainClass.set("org.openrs2.archive.ArchiveCommandKt")
}
dependencies {
api(libs.bundles.guice)
api(libs.clikt)
implementation(projects.asm)
implementation(projects.buffer)
implementation(projects.cache550)
implementation(projects.cli)
implementation(projects.compress)
implementation(projects.db)
implementation(projects.http)
implementation(projects.inject)
implementation(projects.json)
implementation(projects.log)
implementation(projects.net)
implementation(projects.protocol)
implementation(projects.util)
implementation(projects.yaml)
implementation(libs.bootstrap)
implementation(libs.bootstrapTable)
implementation(libs.bundles.ktor)
implementation(libs.bundles.thymeleaf)
implementation(libs.byteUnits)
implementation(libs.cabParser)
implementation(libs.flyway)
implementation(libs.guava)
implementation(libs.hikaricp)
implementation(libs.jackson.jsr310)
implementation(libs.jdom)
implementation(libs.jelf)
implementation(libs.jquery)
implementation(libs.jsoup)
implementation(libs.kotlin.coroutines.core)
implementation(libs.netty.handler)
implementation(libs.pecoff4j)
implementation(libs.postgres)
}
publishing {
publications.create<MavenPublication>("maven") {
from(components["java"])
pom {
packaging = "jar"
name.set("OpenRS2 Archive")
description.set(
"""
Service for archiving clients, caches and XTEA keys in an
efficient deduplicated format.
""".trimIndent()
)
}
}
}

@ -0,0 +1,23 @@
package org.openrs2.archive
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
import org.openrs2.archive.cache.CacheCommand
import org.openrs2.archive.client.ClientCommand
import org.openrs2.archive.key.KeyCommand
import org.openrs2.archive.name.NameCommand
import org.openrs2.archive.web.WebCommand
public fun main(args: Array<String>): Unit = ArchiveCommand().main(args)
public class ArchiveCommand : NoOpCliktCommand(name = "archive") {
init {
subcommands(
CacheCommand(),
ClientCommand(),
KeyCommand(),
NameCommand(),
WebCommand()
)
}
}

@ -0,0 +1,3 @@
package org.openrs2.archive
public data class ArchiveConfig(val databaseUrl: String)

@ -0,0 +1,27 @@
package org.openrs2.archive
import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.inject.Inject
import jakarta.inject.Provider
import org.openrs2.yaml.Yaml
import java.nio.file.Files
import java.nio.file.Path
public class ArchiveConfigProvider @Inject constructor(
@Yaml private val mapper: ObjectMapper
) : Provider<ArchiveConfig> {
override fun get(): ArchiveConfig {
if (Files.notExists(CONFIG_PATH)) {
Files.copy(EXAMPLE_CONFIG_PATH, CONFIG_PATH)
}
return Files.newBufferedReader(CONFIG_PATH).use { reader ->
mapper.readValue(reader, ArchiveConfig::class.java)
}
}
private companion object {
private val CONFIG_PATH = Path.of("etc/archive.yaml")
private val EXAMPLE_CONFIG_PATH = Path.of("etc/archive.example.yaml")
}
}

@ -0,0 +1,55 @@
package org.openrs2.archive
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.google.inject.AbstractModule
import com.google.inject.Scopes
import com.google.inject.multibindings.Multibinder
import org.openrs2.archive.key.HdosKeyDownloader
import org.openrs2.archive.key.KeyDownloader
import org.openrs2.archive.key.RuneLiteKeyDownloader
import org.openrs2.archive.name.NameDownloader
import org.openrs2.archive.name.RuneStarNameDownloader
import org.openrs2.asm.AsmModule
import org.openrs2.buffer.BufferModule
import org.openrs2.cache.CacheModule
import org.openrs2.db.Database
import org.openrs2.http.HttpModule
import org.openrs2.json.JsonModule
import org.openrs2.net.NetworkModule
import org.openrs2.yaml.YamlModule
import javax.sql.DataSource
public object ArchiveModule : AbstractModule() {
override fun configure() {
install(AsmModule)
install(BufferModule)
install(CacheModule)
install(HttpModule)
install(JsonModule)
install(NetworkModule)
install(YamlModule)
bind(ArchiveConfig::class.java)
.toProvider(ArchiveConfigProvider::class.java)
.`in`(Scopes.SINGLETON)
bind(DataSource::class.java)
.toProvider(DataSourceProvider::class.java)
.`in`(Scopes.SINGLETON)
bind(Database::class.java)
.toProvider(DatabaseProvider::class.java)
.`in`(Scopes.SINGLETON)
Multibinder.newSetBinder(binder(), Module::class.java)
.addBinding().to(JavaTimeModule::class.java)
val keyBinder = Multibinder.newSetBinder(binder(), KeyDownloader::class.java)
keyBinder.addBinding().to(HdosKeyDownloader::class.java)
keyBinder.addBinding().to(RuneLiteKeyDownloader::class.java)
val nameBinder = Multibinder.newSetBinder(binder(), NameDownloader::class.java)
nameBinder.addBinding().to(RuneStarNameDownloader::class.java)
}
}

@ -0,0 +1,28 @@
package org.openrs2.archive
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import jakarta.inject.Inject
import jakarta.inject.Provider
import org.flywaydb.core.Flyway
import org.postgresql.ds.PGSimpleDataSource
import javax.sql.DataSource
public class DataSourceProvider @Inject constructor(
private val config: ArchiveConfig
) : Provider<DataSource> {
override fun get(): DataSource {
val dataSource = PGSimpleDataSource()
dataSource.setUrl(config.databaseUrl)
Flyway.configure()
.dataSource(dataSource)
.locations("classpath:/org/openrs2/archive/migrations")
.load()
.migrate()
val config = HikariConfig()
config.dataSource = dataSource
return HikariDataSource(config)
}
}

@ -0,0 +1,15 @@
package org.openrs2.archive
import jakarta.inject.Inject
import jakarta.inject.Provider
import org.openrs2.db.Database
import org.openrs2.db.PostgresDeadlockDetector
import javax.sql.DataSource
public class DatabaseProvider @Inject constructor(
private val dataSource: DataSource
) : Provider<Database> {
override fun get(): Database {
return Database(dataSource, deadlockDetector = PostgresDeadlockDetector)
}
}

@ -0,0 +1,19 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
import org.openrs2.archive.cache.finder.ExtractCommand
public class CacheCommand : NoOpCliktCommand(name = "cache") {
init {
subcommands(
CrossPollinateCommand(),
DownloadCommand(),
ExtractCommand(),
ImportCommand(),
ImportMasterIndexCommand(),
ExportCommand(),
RefreshViewsCommand()
)
}
}

@ -0,0 +1,158 @@
package org.openrs2.archive.cache
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.openrs2.archive.cache.nxt.MusicStreamClient
import org.openrs2.archive.game.GameDatabase
import org.openrs2.archive.jav.JavConfig
import org.openrs2.archive.world.World
import org.openrs2.archive.world.WorldList
import org.openrs2.buffer.ByteBufBodyHandler
import org.openrs2.buffer.use
import org.openrs2.net.BootstrapFactory
import org.openrs2.net.awaitSuspend
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Singleton
public class CacheDownloader @Inject constructor(
private val client: HttpClient,
private val byteBufBodyHandler: ByteBufBodyHandler,
private val bootstrapFactory: BootstrapFactory,
private val gameDatabase: GameDatabase,
private val importer: CacheImporter
) {
public suspend fun download(gameName: String, environment: String, language: String) {
val game = gameDatabase.getGame(gameName, environment, language) ?: throw Exception("Game not found")
val url = game.url ?: throw Exception("URL not set")
val config = JavConfig.download(client, url)
val group = bootstrapFactory.createEventLoopGroup()
try {
suspendCoroutine { continuation ->
val bootstrap = bootstrapFactory.createBootstrap(group)
val hostname: String
val initializer = when (gameName) {
"oldschool" -> {
var buildMajor = game.buildMajor
hostname = if (environment == "beta") {
findOsrsWorld(config, World::isBeta) ?: throw Exception("Failed to find beta world")
} else {
val codebase = config.config[CODEBASE] ?: throw Exception("Codebase missing")
URI(codebase).host ?: throw Exception("Hostname missing")
}
val serverVersion = config.params[OSRS_SERVER_VERSION]
if (serverVersion != null) {
buildMajor = serverVersion.toInt()
}
OsrsJs5ChannelInitializer(
OsrsJs5ChannelHandler(
bootstrap,
game.scopeId,
game.id,
hostname,
PORT,
buildMajor ?: throw Exception("Current major build not set"),
game.lastMasterIndexId,
continuation,
importer
)
)
}
"runescape" -> {
var buildMajor = game.buildMajor
var buildMinor = game.buildMinor
val serverVersion = config.config[NXT_SERVER_VERSION]
if (serverVersion != null) {
val n = serverVersion.toInt()
/*
* Only reset buildMinor if buildMajor changes, so
* we don't have to keep retrying minor versions.
*/
if (buildMajor != n) {
buildMajor = n
buildMinor = 1
}
}
val tokens = config.params.values.filter { TOKEN_REGEX.matches(it) }
val token = tokens.singleOrNull() ?: throw Exception("Multiple candidate tokens: $tokens")
hostname = if (environment == "beta") {
NXT_BETA_HOSTNAME
} else {
NXT_LIVE_HOSTNAME
}
val musicStreamClient = MusicStreamClient(client, byteBufBodyHandler, "http://$hostname")
NxtJs5ChannelInitializer(
NxtJs5ChannelHandler(
bootstrap,
game.scopeId,
game.id,
hostname,
PORT,
buildMajor ?: throw Exception("Current major build not set"),
buildMinor ?: throw Exception("Current minor build not set"),
game.lastMasterIndexId,
continuation,
importer,
token,
game.languageId,
musicStreamClient
)
)
}
else -> throw UnsupportedOperationException()
}
bootstrap.handler(initializer)
.connect(hostname, PORT)
.addListener { future ->
if (!future.isSuccess) {
continuation.resumeWithException(future.cause())
}
}
}
} finally {
group.shutdownGracefully().awaitSuspend()
}
}
private fun findOsrsWorld(config: JavConfig, predicate: (World) -> Boolean): String? {
val url = config.params[OSRS_WORLD_LIST_URL] ?: throw Exception("World list URL missing")
val list = client.send(HttpRequest.newBuilder(URI(url)).build(), byteBufBodyHandler).body().use { buf ->
WorldList.read(buf)
}
return list.worlds
.filter(predicate)
.map(World::hostname)
.shuffled()
.firstOrNull()
}
private companion object {
private const val CODEBASE = "codebase"
private const val OSRS_WORLD_LIST_URL = "17"
private const val OSRS_SERVER_VERSION = "25"
private const val NXT_SERVER_VERSION = "server_version"
private const val NXT_LIVE_HOSTNAME = "content.runescape.com"
private const val NXT_BETA_HOSTNAME = "content.beta.runescape.com"
private const val PORT = 443
private val TOKEN_REGEX = Regex("[A-Za-z0-9*-]{32}")
}
}

@ -0,0 +1,806 @@
package org.openrs2.archive.cache
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonUnwrapped
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufAllocator
import io.netty.buffer.Unpooled
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.openrs2.buffer.use
import org.openrs2.cache.ChecksumTable
import org.openrs2.cache.DiskStore
import org.openrs2.cache.Js5Archive
import org.openrs2.cache.Js5Compression
import org.openrs2.cache.Js5MasterIndex
import org.openrs2.cache.MasterIndexFormat
import org.openrs2.cache.Store
import org.openrs2.crypto.SymmetricKey
import org.openrs2.db.Database
import org.postgresql.util.PGobject
import java.sql.Connection
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.SortedSet
@Singleton
public class CacheExporter @Inject constructor(
private val database: Database,
private val alloc: ByteBufAllocator
) {
public data class Stats(
val validIndexes: Long,
val indexes: Long,
val validGroups: Long,
val groups: Long,
val validKeys: Long,
val keys: Long,
val size: Long,
val blocks: Long
) {
@JsonIgnore
public val allIndexesValid: Boolean = indexes == validIndexes && indexes != 0L
@JsonIgnore
public val validIndexesFraction: Double = if (indexes == 0L) {
1.0
} else {
validIndexes.toDouble() / indexes
}
@JsonIgnore
public val allGroupsValid: Boolean = groups == validGroups
@JsonIgnore
public val validGroupsFraction: Double = if (groups == 0L) {
1.0
} else {
validGroups.toDouble() / groups
}
@JsonIgnore
public val allKeysValid: Boolean = keys == validKeys
@JsonIgnore
public val validKeysFraction: Double = if (keys == 0L) {
1.0
} else {
validKeys.toDouble() / keys
}
/*
* The max block ID is conveniently also the max number of blocks, as
* zero is reserved.
*/
public val diskStoreValid: Boolean = blocks <= DiskStore.MAX_BLOCK
}
public data class Archive(
val resolved: Boolean,
val stats: ArchiveStats?
)
public data class ArchiveStats(
val validGroups: Long,
val groups: Long,
val validKeys: Long,
val keys: Long,
val size: Long,
val blocks: Long
) {
public val allGroupsValid: Boolean = groups == validGroups
public val validGroupsFraction: Double = if (groups == 0L) {
1.0
} else {
validGroups.toDouble() / groups
}
public val allKeysValid: Boolean = keys == validKeys
public val validKeysFraction: Double = if (keys == 0L) {
1.0
} else {
validKeys.toDouble() / keys
}
}
public data class IndexStats(
val validFiles: Long,
val files: Long,
val size: Long,
val blocks: Long
) {
public val allFilesValid: Boolean = files == validFiles
public val validFilesFraction: Double = if (files == 0L) {
1.0
} else {
validFiles.toDouble() / files
}
}
public data class Build(val major: Int, val minor: Int?) : Comparable<Build> {
override fun compareTo(other: Build): Int {
return compareValuesBy(this, other, Build::major, Build::minor)
}
override fun toString(): String {
return if (minor != null) {
"$major.$minor"
} else {
major.toString()
}
}
internal companion object {
internal fun fromPgObject(o: PGobject): Build? {
val value = o.value!!
require(value.length >= 2)
val parts = value.substring(1, value.length - 1).split(",")
require(parts.size == 2)
val major = parts[0]
val minor = parts[1]
if (major.isEmpty()) {
return null
}
return Build(major.toInt(), if (minor.isEmpty()) null else minor.toInt())
}
}
}
public data class CacheSummary(
val id: Int,
val scope: String,
val game: String,
val environment: String,
val language: String,
val builds: SortedSet<Build>,
val timestamp: Instant?,
val sources: SortedSet<String>,
@JsonUnwrapped
val stats: Stats?
)
public data class Cache(
val id: Int,
val sources: List<Source>,
val updates: List<String>,
val stats: Stats?,
val archives: List<Archive>,
val indexes: List<IndexStats>?,
val masterIndex: Js5MasterIndex?,
val checksumTable: ChecksumTable?
)
public data class Source(
val game: String,
val environment: String,
val language: String,
val build: Build?,
val timestamp: Instant?,
val name: String?,
val description: String?,
val url: String?
)
public data class Key(
val archive: Int,
val group: Int,
val nameHash: Int?,
val name: String?,
@JsonProperty("mapsquare") val mapSquare: Int?,
val key: SymmetricKey
)
public suspend fun totalSize(): Long {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT SUM(size)
FROM cache_stats
""".trimIndent()
).use { stmt ->
stmt.executeQuery().use { rows ->
if (rows.next()) {
rows.getLong(1)
} else {
0
}
}
}
}
}
public suspend fun list(): List<CacheSummary> {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT *
FROM (
SELECT
c.id,
g.name AS game,
sc.name AS scope,
e.name AS environment,
l.iso_code AS language,
array_remove(array_agg(DISTINCT ROW(s.build_major, s.build_minor)::build ORDER BY ROW(s.build_major, s.build_minor)::build ASC), NULL) builds,
MIN(s.timestamp) AS timestamp,
array_remove(array_agg(DISTINCT s.name ORDER BY s.name ASC), NULL) sources,
cs.valid_indexes,
cs.indexes,
cs.valid_groups,
cs.groups,
cs.valid_keys,
cs.keys,
cs.size,
cs.blocks
FROM caches c
JOIN sources s ON s.cache_id = c.id
JOIN game_variants v ON v.id = s.game_id
JOIN games g ON g.id = v.game_id
JOIN scopes sc ON sc.id = g.scope_id
JOIN environments e ON e.id = v.environment_id
JOIN languages l ON l.id = v.language_id
LEFT JOIN cache_stats cs ON cs.scope_id = sc.id AND cs.cache_id = c.id
WHERE NOT c.hidden
GROUP BY sc.name, c.id, g.name, e.name, l.iso_code, cs.valid_indexes, cs.indexes, cs.valid_groups,
cs.groups, cs.valid_keys, cs.keys, cs.size, cs.blocks
) t
ORDER BY t.game ASC, t.environment ASC, t.language ASC, t.builds[1] ASC, t.timestamp ASC
""".trimIndent()
).use { stmt ->
stmt.executeQuery().use { rows ->
val caches = mutableListOf<CacheSummary>()
while (rows.next()) {
val id = rows.getInt(1)
val game = rows.getString(2)
val scope = rows.getString(3)
val environment = rows.getString(4)
val language = rows.getString(5)
val builds = rows.getArray(6).array as Array<*>
val timestamp = rows.getTimestamp(7)?.toInstant()
@Suppress("UNCHECKED_CAST")
val sources = rows.getArray(8).array as Array<String>
val validIndexes = rows.getLong(9)
val stats = if (!rows.wasNull()) {
val indexes = rows.getLong(10)
val validGroups = rows.getLong(11)
val groups = rows.getLong(12)
val validKeys = rows.getLong(13)
val keys = rows.getLong(14)
val size = rows.getLong(15)
val blocks = rows.getLong(16)
Stats(validIndexes, indexes, validGroups, groups, validKeys, keys, size, blocks)
} else {
null
}
caches += CacheSummary(
id,
scope,
game,
environment,
language,
builds.mapNotNull { o -> Build.fromPgObject(o as PGobject) }.toSortedSet(),
timestamp,
sources.toSortedSet(),
stats
)
}
caches
}
}
}
}
public suspend fun get(scope: String, id: Int): Cache? {
return database.execute { connection ->
val masterIndex: Js5MasterIndex?
val checksumTable: ChecksumTable?
val stats: Stats?
connection.prepareStatement(
"""
SELECT
m.format,
mc.data,
b.data,
cs.valid_indexes,
cs.indexes,
cs.valid_groups,
cs.groups,
cs.valid_keys,
cs.keys,
cs.size,
cs.blocks
FROM caches c
CROSS JOIN scopes s
LEFT JOIN master_indexes m ON m.id = c.id
LEFT JOIN containers mc ON mc.id = m.container_id
LEFT JOIN crc_tables t ON t.id = c.id
LEFT JOIN blobs b ON b.id = t.blob_id
LEFT JOIN cache_stats cs ON cs.scope_id = s.id AND cs.cache_id = c.id
WHERE s.name = ? AND c.id = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val formatString = rows.getString(1)
masterIndex = if (formatString != null) {
Unpooled.wrappedBuffer(rows.getBytes(2)).use { compressed ->
Js5Compression.uncompress(compressed).use { uncompressed ->
val format = MasterIndexFormat.valueOf(formatString.uppercase())
Js5MasterIndex.readUnverified(uncompressed, format)
}
}
} else {
null
}
val blob = rows.getBytes(3)
checksumTable = if (blob != null) {
Unpooled.wrappedBuffer(blob).use { buf ->
ChecksumTable.read(buf)
}
} else {
null
}
val validIndexes = rows.getLong(4)
stats = if (rows.wasNull()) {
null
} else {
val indexes = rows.getLong(5)
val validGroups = rows.getLong(6)
val groups = rows.getLong(7)
val validKeys = rows.getLong(8)
val keys = rows.getLong(9)
val size = rows.getLong(10)
val blocks = rows.getLong(11)
Stats(validIndexes, indexes, validGroups, groups, validKeys, keys, size, blocks)
}
}
}
val sources = mutableListOf<Source>()
connection.prepareStatement(
"""
SELECT g.name, e.name, l.iso_code, s.build_major, s.build_minor, s.timestamp, s.name, s.description, s.url
FROM sources s
JOIN game_variants v ON v.id = s.game_id
JOIN games g ON g.id = v.game_id
JOIN scopes sc ON sc.id = g.scope_id
JOIN environments e ON e.id = v.environment_id
JOIN languages l ON l.id = v.language_id
WHERE sc.name = ? AND s.cache_id = ?
ORDER BY s.name ASC
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val game = rows.getString(1)
val environment = rows.getString(2)
val language = rows.getString(3)
var buildMajor: Int? = rows.getInt(4)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(5)
if (rows.wasNull()) {
buildMinor = null
}
val build = if (buildMajor != null) {
Build(buildMajor, buildMinor)
} else {
null
}
val timestamp = rows.getTimestamp(6)?.toInstant()
val name = rows.getString(7)
val description = rows.getString(8)
val url = rows.getString(9)
sources += Source(game, environment, language, build, timestamp, name, description, url)
}
}
}
val updates = mutableListOf<String>()
connection.prepareStatement(
"""
SELECT url
FROM updates
WHERE cache_id = ?
""".trimIndent()
).use { stmt ->
stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
updates += rows.getString(1)
}
}
}
val archives = mutableListOf<Archive>()
connection.prepareStatement(
"""
SELECT a.archive_id, c.id IS NOT NULL, s.valid_groups, s.groups, s.valid_keys, s.keys, s.size, s.blocks
FROM master_index_archives a
LEFT JOIN resolve_index((SELECT id FROM scopes WHERE name = ?), a.archive_id, a.crc32, a.version) c ON TRUE
LEFT JOIN index_stats s ON s.container_id = c.id
WHERE a.master_index_id = ?
UNION ALL
SELECT a.archive_id, b.id IS NOT NULL, NULL, NULL, NULL, NULL, length(b.data), group_blocks(a.archive_id, length(b.data))
FROM crc_table_archives a
LEFT JOIN resolve_archive(a.archive_id, a.crc32) b ON TRUE
WHERE a.crc_table_id = ?
ORDER BY archive_id ASC
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.setInt(3, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val resolved = rows.getBoolean(2)
val size = rows.getLong(7)
val archiveStats = if (!rows.wasNull()) {
val validGroups = rows.getLong(3)
val groups = rows.getLong(4)
val validKeys = rows.getLong(5)
val keys = rows.getLong(6)
val blocks = rows.getLong(8)
ArchiveStats(validGroups, groups, validKeys, keys, size, blocks)
} else {
null
}
archives += Archive(resolved, archiveStats)
}
}
}
val indexes = if (checksumTable != null && archives[5].resolved) {
connection.prepareStatement(
"""
SELECT s.valid_files, s.files, s.size, s.blocks
FROM crc_table_archives a
JOIN resolve_archive(a.archive_id, a.crc32) b ON TRUE
JOIN version_list_stats s ON s.blob_id = b.id
WHERE a.crc_table_id = ? AND a.archive_id = 5
ORDER BY s.index_id ASC
""".trimIndent()
).use { stmt ->
stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
val indexes = mutableListOf<IndexStats>()
while (rows.next()) {
val validFiles = rows.getLong(1)
val files = rows.getLong(2)
val size = rows.getLong(3)
val blocks = rows.getLong(4)
indexes += IndexStats(validFiles, files, size, blocks)
}
indexes
}
}
} else {
null
}
Cache(id, sources, updates, stats, archives, indexes, masterIndex, checksumTable)
}
}
public suspend fun getFileName(scope: String, id: Int): String? {
return database.execute { connection ->
// TODO(gpe): what if a cache is from multiple games?
connection.prepareStatement(
"""
SELECT
g.name AS game,
e.name AS environment,
l.iso_code AS language,
array_remove(array_agg(DISTINCT ROW(s.build_major, s.build_minor)::build ORDER BY ROW(s.build_major, s.build_minor)::build ASC), NULL) builds,
MIN(s.timestamp) AS timestamp
FROM sources s
JOIN game_variants v ON v.id = s.game_id
JOIN games g ON g.id = v.game_id
JOIN scopes sc ON sc.id = g.scope_id
JOIN environments e ON e.id = v.environment_id
JOIN languages l ON l.id = v.language_id
WHERE sc.name = ? AND s.cache_id = ?
GROUP BY g.name, e.name, l.iso_code
LIMIT 1
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val game = rows.getString(1)
val environment = rows.getString(2)
val language = rows.getString(3)
val name = StringBuilder("$game-$environment-$language")
val builds = rows.getArray(4).array as Array<*>
for (build in builds.mapNotNull { o -> Build.fromPgObject(o as PGobject) }.toSortedSet()) {
name.append("-b")
name.append(build)
}
val timestamp = rows.getTimestamp(5)
if (!rows.wasNull()) {
name.append('-')
name.append(
timestamp.toInstant()
.atOffset(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"))
)
}
name.append("-openrs2#")
name.append(id)
name.toString()
}
}
}
}
public suspend fun exportGroup(scope: String, id: Int, archive: Int, group: Int): ByteBuf? {
return database.execute { connection ->
if (archive == Store.ARCHIVESET && group == Store.ARCHIVESET) {
connection.prepareStatement(
"""
SELECT c.data
FROM master_indexes m
JOIN containers c ON c.id = m.container_id
WHERE m.id = ?
""".trimIndent()
).use { stmt ->
stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
if (rows.next()) {
val data = rows.getBytes(1)
return@execute Unpooled.wrappedBuffer(data)
}
}
}
}
connection.prepareStatement(
"""
SELECT g.data
FROM resolved_groups g
JOIN scopes s ON s.id = g.scope_id
WHERE s.name = ? AND g.master_index_id = ? AND g.archive_id = ? AND g.group_id = ?
UNION ALL
SELECT f.data
FROM resolved_files f
WHERE f.crc_table_id = ? AND f.index_id = ? AND f.file_id = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.setInt(3, archive)
stmt.setInt(4, group)
stmt.setInt(5, id)
stmt.setInt(6, archive)
stmt.setInt(7, group)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val data = rows.getBytes(1)
return@execute Unpooled.wrappedBuffer(data)
}
}
}
}
public fun export(scope: String, id: Int, storeFactory: (Boolean) -> Store) {
database.executeOnce { connection ->
val legacy = connection.prepareStatement(
"""
SELECT id
FROM crc_tables
WHERE id = ?
""".trimIndent()
).use { stmt ->
stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
rows.next()
}
}
storeFactory(legacy).use { store ->
if (legacy) {
exportLegacy(connection, id, store)
} else {
export(connection, scope, id, store)
}
}
}
}
private fun export(connection: Connection, scope: String, id: Int, store: Store) {
connection.prepareStatement(
"""
SELECT g.archive_id, g.group_id, g.data, g.version
FROM resolved_groups g
JOIN scopes s ON s.id = g.scope_id
WHERE s.name = ? AND g.master_index_id = ?
""".trimIndent()
).use { stmt ->
stmt.fetchSize = BATCH_SIZE
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.executeQuery().use { rows ->
alloc.buffer(2, 2).use { versionBuf ->
store.create(Js5Archive.ARCHIVESET)
while (rows.next()) {
val archive = rows.getInt(1)
val group = rows.getInt(2)
val bytes = rows.getBytes(3)
val version = rows.getInt(4)
val versionNull = rows.wasNull()
versionBuf.clear()
if (!versionNull) {
versionBuf.writeShort(version)
}
Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(bytes), versionBuf.retain()).use { buf ->
store.write(archive, group, buf)
// ensure the .idx file exists even if it is empty
if (archive == Js5Archive.ARCHIVESET) {
store.create(group)
}
}
}
}
}
}
}
private fun exportLegacy(connection: Connection, id: Int, store: Store) {
connection.prepareStatement(
"""
SELECT index_id, file_id, data, version
FROM resolved_files
WHERE crc_table_id = ?
""".trimIndent()
).use { stmt ->
stmt.fetchSize = BATCH_SIZE
stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
alloc.buffer(2, 2).use { versionBuf ->
store.create(0)
while (rows.next()) {
val index = rows.getInt(1)
val file = rows.getInt(2)
val bytes = rows.getBytes(3)
val version = rows.getInt(4)
val versionNull = rows.wasNull()
versionBuf.clear()
if (!versionNull) {
versionBuf.writeShort(version)
}
Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(bytes), versionBuf.retain()).use { buf ->
store.write(index, file, buf)
}
}
}
}
}
}
public suspend fun exportKeys(scope: String, id: Int): List<Key> {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT g.archive_id, g.group_id, g.name_hash, n.name, (k.key).k0, (k.key).k1, (k.key).k2, (k.key).k3
FROM resolved_groups g
JOIN scopes s ON s.id = g.scope_id
JOIN keys k ON k.id = g.key_id
LEFT JOIN names n ON n.hash = g.name_hash AND n.name ~ '^l(?:[0-9]|[1-9][0-9])_(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
WHERE s.name = ? AND g.master_index_id = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, scope)
stmt.setInt(2, id)
stmt.executeQuery().use { rows ->
val keys = mutableListOf<Key>()
while (rows.next()) {
val archive = rows.getInt(1)
val group = rows.getInt(2)
var nameHash: Int? = rows.getInt(3)
if (rows.wasNull()) {
nameHash = null
}
val name = rows.getString(4)
val k0 = rows.getInt(5)
val k1 = rows.getInt(6)
val k2 = rows.getInt(7)
val k3 = rows.getInt(8)
val mapSquare = getMapSquare(name)
keys += Key(archive, group, nameHash, name, mapSquare, SymmetricKey(k0, k1, k2, k3))
}
keys
}
}
}
}
private companion object {
private const val BATCH_SIZE = 256
private val LOC_NAME_REGEX = Regex("l(\\d+)_(\\d+)")
private fun getMapSquare(name: String?): Int? {
if (name == null) {
return null
}
val match = LOC_NAME_REGEX.matchEntire(name) ?: return null
val x = match.groupValues[1].toInt()
val z = match.groupValues[2].toInt()
return (x shl 8) or z
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,16 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class CrossPollinateCommand : CliktCommand(name = "cross-pollinate") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val crossPollinator = injector.getInstance(CrossPollinator::class.java)
crossPollinator.crossPollinate()
}
}
}

@ -0,0 +1,223 @@
package org.openrs2.archive.cache
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufAllocator
import io.netty.buffer.ByteBufInputStream
import io.netty.buffer.Unpooled
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.openrs2.buffer.crc32
import org.openrs2.buffer.use
import org.openrs2.cache.Js5Compression
import org.openrs2.cache.Js5CompressionType
import org.openrs2.db.Database
import java.sql.Connection
import java.util.zip.GZIPInputStream
@Singleton
public class CrossPollinator @Inject constructor(
private val database: Database,
private val alloc: ByteBufAllocator,
private val importer: CacheImporter
) {
public suspend fun crossPollinate() {
database.execute { connection ->
for ((index, archive) in OLD_TO_NEW_ENGINE) {
crossPollinate(connection, index, archive)
}
}
}
private fun crossPollinate(connection: Connection, index: Int, archive: Int) {
val scopeId: Int
connection.prepareStatement(
"""
SELECT id
FROM scopes
WHERE name = 'runescape'
""".trimIndent()
).use { stmt ->
stmt.executeQuery().use { rows ->
check(rows.next())
scopeId = rows.getInt(1)
}
}
val groups = mutableListOf<CacheImporter.Group>()
val files = mutableListOf<CacheImporter.File>()
try {
connection.prepareStatement(
"""
SELECT
new.group_id AS id,
old.version AS old_version,
old.crc32 AS old_crc32,
b.data AS old_data,
new.version AS new_version,
new.crc32 AS new_crc32,
c.data AS new_data
FROM (
SELECT DISTINCT vf.index_id, vf.file_id, vf.version, vf.crc32
FROM version_list_files vf
WHERE vf.blob_id IN (
SELECT v.blob_id
FROM version_lists v
JOIN resolved_archives a ON a.blob_id = v.blob_id AND a.archive_id = 5
) AND vf.index_id = ?
) old
JOIN (
SELECT DISTINCT ig.group_id, ig.version, ig.crc32
FROM index_groups ig
WHERE ig.container_id IN (
SELECT i.container_id
FROM resolved_indexes i
WHERE i.scope_id = ? AND i.archive_id = ?
)
) new ON old.file_id = new.group_id AND old.version = new.version + 1
LEFT JOIN resolve_file(old.index_id, old.file_id, old.version, old.crc32) b ON TRUE
LEFT JOIN resolve_group(?, ?::uint1, new.group_id, new.crc32, new.version) c ON TRUE
WHERE (b.data IS NULL AND c.data IS NOT NULL) OR (b.data IS NOT NULL AND c.data IS NULL)
""".trimIndent()
).use { stmt ->
stmt.setInt(1, index)
stmt.setInt(2, scopeId)
stmt.setInt(3, archive)
stmt.setInt(4, scopeId)
stmt.setInt(5, archive)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val id = rows.getInt(1)
val oldVersion = rows.getInt(2)
val oldChecksum = rows.getInt(3)
val newVersion = rows.getInt(5)
val newChecksum = rows.getInt(6)
val oldData = rows.getBytes(4)
if (oldData != null) {
Unpooled.wrappedBuffer(oldData).use { oldBuf ->
fileToGroup(oldBuf, newChecksum).use { newBuf ->
if (newBuf != null) {
val uncompressed = Js5Compression.uncompressUnlessEncrypted(newBuf.slice())
groups += CacheImporter.Group(
archive,
id,
newBuf.retain(),
uncompressed,
newVersion,
false
)
}
}
}
}
val newData = rows.getBytes(7)
if (newData != null) {
Unpooled.wrappedBuffer(newData).use { newBuf ->
val oldBuf = groupToFile(newBuf, oldChecksum)
if (oldBuf != null) {
files += CacheImporter.File(index, id, oldBuf, oldVersion)
}
}
}
}
}
}
if (groups.isEmpty() && files.isEmpty()) {
return
}
importer.prepare(connection)
val sourceId = importer.addSource(
connection,
type = CacheImporter.SourceType.CROSS_POLLINATION,
cacheId = null,
gameId = null,
buildMajor = null,
buildMinor = null,
timestamp = null,
name = null,
description = null,
url = null,
)
if (groups.isNotEmpty()) {
importer.addGroups(connection, scopeId, sourceId, groups)
}
if (files.isNotEmpty()) {
importer.addFiles(connection, sourceId, files)
}
} finally {
groups.forEach(CacheImporter.Group::release)
files.forEach(CacheImporter.File::release)
}
}
private fun getUncompressedLength(buf: ByteBuf): Int {
GZIPInputStream(ByteBufInputStream(buf)).use { input ->
var len = 0
val temp = ByteArray(4096)
while (true) {
val n = input.read(temp)
if (n == -1) {
break
}
len += n
}
return len
}
}
private fun fileToGroup(input: ByteBuf, expectedChecksum: Int): ByteBuf? {
val len = input.readableBytes()
val lenWithHeader = len + JS5_COMPRESSION_HEADER_LEN
val uncompressedLen = getUncompressedLength(input.slice())
alloc.buffer(lenWithHeader, lenWithHeader).use { output ->
output.writeByte(Js5CompressionType.GZIP.ordinal)
output.writeInt(len)
output.writeInt(uncompressedLen)
output.writeBytes(input)
return if (output.crc32() == expectedChecksum) {
output.retain()
} else {
null
}
}
}
private fun groupToFile(input: ByteBuf, expectedChecksum: Int): ByteBuf? {
val type = Js5CompressionType.fromOrdinal(input.readUnsignedByte().toInt())
if (type != Js5CompressionType.GZIP) {
return null
}
input.skipBytes(JS5_COMPRESSION_HEADER_LEN - 1)
return if (input.crc32() == expectedChecksum) {
input.retainedSlice()
} else {
null
}
}
private companion object {
private val OLD_TO_NEW_ENGINE = mapOf(
1 to 7, // MODELS
3 to 6, // MIDI_SONGS
4 to 5, // MAPS
)
private const val JS5_COMPRESSION_HEADER_LEN = 9
}
}

@ -0,0 +1,25 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.default
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class DownloadCommand : CliktCommand(name = "download") {
private val environment by option().default("live")
private val language by option().default("en")
private val game by argument().default("oldschool")
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val downloader = injector.getInstance(CacheDownloader::class.java)
downloader.download(game, environment, language)
}
}
}

@ -0,0 +1,34 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.cache.DiskStore
import org.openrs2.inject.CloseableInjector
public class ExportCommand : CliktCommand(name = "export") {
private val scope by option().default("runescape")
private val id by argument().int()
private val output by argument().path(
mustExist = true,
canBeFile = false,
mustBeReadable = true,
mustBeWritable = true
)
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val exporter = injector.getInstance(CacheExporter::class.java)
exporter.export(scope, id) { legacy ->
DiskStore.create(output, legacy = legacy)
}
}
}
}

@ -0,0 +1,53 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.cache.Store
import org.openrs2.cli.instant
import org.openrs2.inject.CloseableInjector
public class ImportCommand : CliktCommand(name = "import") {
private val buildMajor by option().int()
private val buildMinor by option().int()
private val timestamp by option().instant()
private val name by option()
private val description by option()
private val url by option()
private val environment by option().default("live")
private val language by option().default("en")
private val game by argument()
private val input by argument().path(
mustExist = true,
canBeFile = false,
mustBeReadable = true
)
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(CacheImporter::class.java)
Store.open(input).use { store ->
importer.import(
store,
game,
environment,
language,
buildMajor,
buildMinor,
timestamp,
name,
description,
url
)
}
}
}
}

@ -0,0 +1,116 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import com.google.inject.Guice
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.buffer.use
import org.openrs2.cache.Js5CompressionType
import org.openrs2.cache.MasterIndexFormat
import org.openrs2.cli.instant
import org.openrs2.inject.CloseableInjector
import java.io.IOException
import java.nio.file.Files
import kotlin.math.min
public class ImportMasterIndexCommand : CliktCommand(name = "import-master-index") {
private val buildMajor by option().int()
private val buildMinor by option().int()
private val timestamp by option().instant()
private val name by option()
private val description by option()
private val url by option()
private val environment by option().default("live")
private val language by option().default("en")
private val decodeJs5Response by option().flag()
private val game by argument()
private val format by argument().enum<MasterIndexFormat>()
private val input by argument().path(
mustExist = true,
canBeDir = false,
mustBeReadable = true
)
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(CacheImporter::class.java)
Unpooled.wrappedBuffer(Files.readAllBytes(input)).use { buf ->
if (decodeJs5Response) {
decodeJs5Response(buf)
} else {
buf.retain()
}.use { decodedBuf ->
importer.importMasterIndex(
decodedBuf,
format,
game,
environment,
language,
buildMajor,
buildMinor,
timestamp,
name,
description,
url
)
}
}
}
}
private fun decodeJs5Response(input: ByteBuf): ByteBuf {
input.skipBytes(3) // archive and group
val compression = input.readUnsignedByte().toInt()
val len = input.readInt()
if (len < 0) {
throw IOException("Length is negative: $len")
}
val lenWithHeader = if (compression == Js5CompressionType.UNCOMPRESSED.ordinal) {
len + 5
} else {
len + 9
}
input.alloc().buffer(lenWithHeader, lenWithHeader).use { output ->
output.writeByte(compression)
output.writeInt(len)
var blockLen = 504
while (true) {
val n = min(blockLen, output.writableBytes())
if (input.readableBytes() < n) {
throw IOException("Input truncated (expecting $n bytes, got ${input.readableBytes()})")
}
output.writeBytes(input, n)
if (!output.isWritable) {
break
} else if (!input.isReadable) {
throw IOException("Input truncated (expecting block trailer)")
}
if (input.readUnsignedByte().toInt() != 0xFF) {
throw IOException("Invalid block trailer")
}
blockLen = 511
}
return output.retain()
}
}
}

@ -0,0 +1,376 @@
package org.openrs2.archive.cache
import com.github.michaelbull.logging.InlineLogger
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelException
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPipeline
import io.netty.channel.SimpleChannelInboundHandler
import kotlinx.coroutines.runBlocking
import org.openrs2.buffer.crc32
import org.openrs2.buffer.use
import org.openrs2.cache.Js5Archive
import org.openrs2.cache.Js5Compression
import org.openrs2.cache.Js5Index
import org.openrs2.cache.Js5MasterIndex
import org.openrs2.cache.MasterIndexFormat
import java.io.IOException
import java.nio.channels.ClosedChannelException
import java.time.Instant
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@ChannelHandler.Sharable
public abstract class Js5ChannelHandler(
private val bootstrap: Bootstrap,
private val scopeId: Int,
private val gameId: Int,
private val hostname: String,
private val port: Int,
protected var buildMajor: Int,
protected var buildMinor: Int?,
private val lastMasterIndexId: Int?,
private val continuation: Continuation<Unit>,
private val importer: CacheImporter,
private val masterIndexFormat: MasterIndexFormat,
private val maxInFlightRequests: Int,
private val maxBuildAttempts: Int = 10,
private val maxReconnectionAttempts: Int = 1
) : SimpleChannelInboundHandler<Any>(Object::class.java) {
protected data class InFlightRequest(val prefetch: Boolean, val archive: Int, val group: Int)
protected data class PendingRequest(
val prefetch: Boolean,
val archive: Int,
val group: Int,
val version: Int,
val checksum: Int
)
private enum class State {
CONNECTING,
CLIENT_OUT_OF_DATE,
CONNECTED,
RESUMING_CONTINUATION
}
private var state = State.CONNECTING
private var buildAttempts = 0
private var reconnectionAttempts = 0
private val inFlightRequests = mutableSetOf<InFlightRequest>()
private val pendingRequests = ArrayDeque<PendingRequest>()
private var masterIndexId: Int = 0
private var sourceId: Int = 0
private var masterIndex: Js5MasterIndex? = null
private lateinit var indexes: Array<Js5Index?>
private val groups = mutableListOf<CacheImporter.Group>()
protected abstract fun createInitMessage(): Any
protected abstract fun createRequestMessage(prefetch: Boolean, archive: Int, group: Int): Any
protected abstract fun createConnectedMessage(): Any?
protected abstract fun configurePipeline(pipeline: ChannelPipeline)
protected abstract fun incrementVersion()
override fun channelActive(ctx: ChannelHandlerContext) {
assert(state == State.CONNECTING)
ctx.writeAndFlush(createInitMessage(), ctx.voidPromise())
ctx.read()
}
override fun channelReadComplete(ctx: ChannelHandlerContext) {
/*
* Wait for us to receive the OK message before we send JS5 requests,
* as the RS3 JS5 server ignores any JS5 requests sent before the OK
* message is received.
*/
if (state != State.CONNECTED) {
return
}
var flush = false
while (inFlightRequests.size < maxInFlightRequests) {
val request = pendingRequests.removeFirstOrNull() ?: break
inFlightRequests += InFlightRequest(request.prefetch, request.archive, request.group)
logger.info { "Requesting archive ${request.archive} group ${request.group}" }
ctx.write(createRequestMessage(request.prefetch, request.archive, request.group), ctx.voidPromise())
flush = true
}
if (flush) {
ctx.flush()
}
if (inFlightRequests.isNotEmpty()) {
ctx.read()
}
}
override fun channelInactive(ctx: ChannelHandlerContext) {
if (state == State.CLIENT_OUT_OF_DATE) {
state = State.CONNECTING
bootstrap.connect(hostname, port)
} else if (state != State.RESUMING_CONTINUATION) {
if (isComplete()) {
throw Exception("Connection closed unexpectedly")
} else if (++reconnectionAttempts > maxReconnectionAttempts) {
throw Exception("Connection closed unexpectedly after maximum number of reconnection attempts")
}
// move in-flight requests back to the pending queue
for (request in inFlightRequests) {
val prefetch = request.prefetch
val archive = request.archive
val group = request.group
pendingRequests += if (archive == Js5Archive.ARCHIVESET && group == Js5Archive.ARCHIVESET) {
PendingRequest(prefetch, archive, group, 0, 0)
} else if (archive == Js5Archive.ARCHIVESET) {
val entry = masterIndex!!.entries[group]
val version = entry.version
val checksum = entry.checksum
PendingRequest(prefetch, archive, group, version, checksum)
} else {
val entry = indexes[archive]!![group]!!
val version = entry.version
val checksum = entry.checksum
PendingRequest(prefetch, archive, group, version, checksum)
}
}
inFlightRequests.clear()
// re-connect
state = State.CONNECTING
bootstrap.connect(hostname, port)
}
}
@Suppress("OVERRIDE_DEPRECATION")
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
releaseGroups()
if (state == State.RESUMING_CONTINUATION) {
logger.warn(cause) { "Swallowing exception as continuation has already resumed" }
} else if (cause !is ChannelException && cause !is IOException) {
/*
* We skip continuation resumption if there's an I/O error or
* timeout - this allows channelInactive() to attempt to reconnect
* if we haven't used too many reconnection attempts.
*/
state = State.RESUMING_CONTINUATION
continuation.resumeWithException(cause)
}
if (cause !is ClosedChannelException) {
ctx.close()
}
}
protected fun handleOk(ctx: ChannelHandlerContext) {
assert(state == State.CONNECTING)
configurePipeline(ctx.pipeline())
val msg = createConnectedMessage()
if (msg != null) {
ctx.write(msg, ctx.voidPromise())
}
state = State.CONNECTED
if (masterIndex == null && pendingRequests.isEmpty()) {
request(ctx, Js5Archive.ARCHIVESET, Js5Archive.ARCHIVESET, 0, 0)
}
}
protected fun handleClientOutOfDate(ctx: ChannelHandlerContext) {
assert(state == State.CONNECTING)
if (++buildAttempts > maxBuildAttempts) {
throw Exception("Failed to identify current version")
}
state = State.CLIENT_OUT_OF_DATE
incrementVersion()
ctx.close()
}
protected fun handleResponse(
ctx: ChannelHandlerContext,
prefetch: Boolean,
archive: Int,
group: Int,
data: ByteBuf
) {
val request = InFlightRequest(prefetch, archive, group)
val removed = inFlightRequests.remove(request)
if (!removed) {
val type = if (prefetch) {
"prefetch"
} else {
"urgent"
}
throw Exception("Received response for $type request (archive $archive group $group) not in-flight")
}
processResponse(ctx, archive, group, data)
}
protected fun processResponse(ctx: ChannelHandlerContext, archive: Int, group: Int, data: ByteBuf) {
if (archive == Js5Archive.ARCHIVESET && group == Js5Archive.ARCHIVESET) {
processMasterIndex(ctx, data)
} else if (archive == Js5Archive.ARCHIVESET) {
processIndex(ctx, group, data)
} else {
processGroup(archive, group, data)
}
val complete = isComplete()
if (groups.size >= CacheImporter.BATCH_SIZE || complete) {
runBlocking {
importer.importGroups(scopeId, sourceId, groups)
}
releaseGroups()
}
if (complete) {
runBlocking {
importer.setLastMasterIndexId(gameId, masterIndexId)
}
state = State.RESUMING_CONTINUATION
continuation.resume(Unit)
ctx.close()
} else {
/*
* Reset the number of reconnection attempts as we are making
* progress.
*/
reconnectionAttempts = 0
}
}
protected open fun isComplete(): Boolean {
return pendingRequests.isEmpty() && inFlightRequests.isEmpty()
}
private fun processMasterIndex(ctx: ChannelHandlerContext, buf: ByteBuf) {
Js5Compression.uncompress(buf.slice()).use { uncompressed ->
masterIndex = Js5MasterIndex.readUnverified(uncompressed.slice(), masterIndexFormat)
val (masterIndexId, sourceId, rawIndexes) = runBlocking {
importer.importMasterIndexAndGetIndexes(
masterIndex!!,
buf,
uncompressed,
gameId,
scopeId,
buildMajor,
buildMinor,
lastMasterIndexId,
timestamp = Instant.now()
)
}
this.masterIndexId = masterIndexId
this.sourceId = sourceId
try {
indexes = arrayOfNulls(rawIndexes.size)
for ((archive, index) in rawIndexes.withIndex()) {
val entry = masterIndex!!.entries[archive]
if (entry.version == 0 && entry.checksum == 0) {
continue
}
if (index != null) {
processIndex(ctx, archive, index)
} else {
request(ctx, Js5Archive.ARCHIVESET, archive, entry.version, entry.checksum)
}
}
} finally {
rawIndexes.filterNotNull().forEach(ByteBuf::release)
}
}
}
private fun processIndex(ctx: ChannelHandlerContext, archive: Int, buf: ByteBuf) {
val checksum = buf.crc32()
val entry = masterIndex!!.entries[archive]
if (checksum != entry.checksum) {
throw Exception("Index $archive checksum invalid (expected ${entry.checksum}, actual $checksum)")
}
Js5Compression.uncompress(buf.slice()).use { uncompressed ->
val index = Js5Index.read(uncompressed.slice())
indexes[archive] = index
if (index.version != entry.version) {
throw Exception("Index $archive version invalid (expected ${entry.version}, actual ${index.version})")
}
val groups = runBlocking {
importer.importIndexAndGetMissingGroups(
scopeId,
sourceId,
archive,
index,
buf,
uncompressed,
lastMasterIndexId
)
}
for (group in groups) {
val groupEntry = index[group]!!
request(ctx, archive, group, groupEntry.version, groupEntry.checksum)
}
}
}
private fun processGroup(archive: Int, group: Int, buf: ByteBuf) {
val checksum = buf.crc32()
val entry = indexes[archive]!![group]!!
if (checksum != entry.checksum) {
val expected = entry.checksum
throw Exception("Archive $archive group $group checksum invalid (expected $expected, actual $checksum)")
}
val uncompressed = Js5Compression.uncompressUnlessEncrypted(buf.slice())
groups += CacheImporter.Group(
archive,
group,
buf.retain(),
uncompressed,
entry.version,
versionTruncated = false
)
}
protected open fun request(ctx: ChannelHandlerContext, archive: Int, group: Int, version: Int, checksum: Int) {
pendingRequests += PendingRequest(false, archive, group, version, checksum)
}
private fun releaseGroups() {
groups.forEach(CacheImporter.Group::release)
groups.clear()
}
private companion object {
private val logger = InlineLogger()
}
}

@ -0,0 +1,158 @@
package org.openrs2.archive.cache
import com.github.michaelbull.logging.InlineLogger
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPipeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.openrs2.archive.cache.nxt.InitJs5RemoteConnection
import org.openrs2.archive.cache.nxt.Js5Request
import org.openrs2.archive.cache.nxt.Js5RequestEncoder
import org.openrs2.archive.cache.nxt.Js5Response
import org.openrs2.archive.cache.nxt.Js5ResponseDecoder
import org.openrs2.archive.cache.nxt.LoginResponse
import org.openrs2.archive.cache.nxt.MusicStreamClient
import org.openrs2.buffer.use
import org.openrs2.cache.MasterIndexFormat
import org.openrs2.protocol.Rs2Decoder
import org.openrs2.protocol.Rs2Encoder
import org.openrs2.protocol.js5.downstream.XorDecoder
import kotlin.coroutines.Continuation
public class NxtJs5ChannelHandler(
bootstrap: Bootstrap,
scopeId: Int,
gameId: Int,
hostname: String,
port: Int,
buildMajor: Int,
buildMinor: Int,
lastMasterIndexId: Int?,
continuation: Continuation<Unit>,
importer: CacheImporter,
private val token: String,
private val languageId: Int,
private val musicStreamClient: MusicStreamClient,
private val maxMinorBuildAttempts: Int = 5
) : Js5ChannelHandler(
bootstrap,
scopeId,
gameId,
hostname,
port,
buildMajor,
buildMinor,
lastMasterIndexId,
continuation,
importer,
MasterIndexFormat.LENGTHS,
maxInFlightRequests = 500
) {
private data class MusicRequest(val archive: Int, val group: Int, val version: Int, val checksum: Int)
private var inFlightRequests = 0
private val pendingRequests = ArrayDeque<MusicRequest>()
private var scope: CoroutineScope? = null
private var minorBuildAttempts = 0
override fun createInitMessage(): Any {
return InitJs5RemoteConnection(buildMajor, buildMinor!!, token, languageId)
}
override fun createRequestMessage(prefetch: Boolean, archive: Int, group: Int): Any {
return Js5Request.Group(prefetch, archive, group, buildMajor)
}
override fun createConnectedMessage(): Any? {
return Js5Request.Connected(buildMajor)
}
override fun configurePipeline(pipeline: ChannelPipeline) {
pipeline.addBefore("handler", null, Js5RequestEncoder)
pipeline.addBefore("handler", null, XorDecoder())
pipeline.addBefore("handler", null, Js5ResponseDecoder())
pipeline.remove(Rs2Encoder::class.java)
pipeline.remove(Rs2Decoder::class.java)
}
override fun incrementVersion() {
buildMinor = buildMinor!! + 1
if (++minorBuildAttempts >= maxMinorBuildAttempts) {
buildMajor++
buildMinor = 1
}
}
override fun channelActive(ctx: ChannelHandlerContext) {
super.channelActive(ctx)
scope = CoroutineScope(ctx.channel().eventLoop().asCoroutineDispatcher())
}
override fun channelInactive(ctx: ChannelHandlerContext) {
super.channelInactive(ctx)
scope!!.cancel()
}
override fun channelRead0(ctx: ChannelHandlerContext, msg: Any) {
when (msg) {
is LoginResponse.Js5Ok -> handleOk(ctx)
is LoginResponse.ClientOutOfDate -> handleClientOutOfDate(ctx)
is LoginResponse -> throw Exception("Invalid response: $msg")
is Js5Response -> handleResponse(ctx, msg.prefetch, msg.archive, msg.group, msg.data)
else -> throw Exception("Unknown message type: ${msg.javaClass.name}")
}
}
override fun channelReadComplete(ctx: ChannelHandlerContext) {
super.channelReadComplete(ctx)
while (inFlightRequests < 6) {
val request = pendingRequests.removeFirstOrNull() ?: break
inFlightRequests++
logger.info { "Requesting archive ${request.archive} group ${request.group}" }
scope!!.launch {
val archive = request.archive
val group = request.group
val version = request.version
val checksum = request.checksum
musicStreamClient.request(archive, group, version, checksum, buildMajor).use { buf ->
inFlightRequests--
processResponse(ctx, archive, group, buf)
/*
* Inject a fake channelReadComplete event to ensure we
* don't time out and to send any new music requests.
*/
ctx.channel().pipeline().fireChannelReadComplete()
}
}
}
}
override fun isComplete(): Boolean {
return super.isComplete() && pendingRequests.isEmpty() && inFlightRequests == 0
}
override fun request(ctx: ChannelHandlerContext, archive: Int, group: Int, version: Int, checksum: Int) {
if (archive == MUSIC_ARCHIVE) {
pendingRequests += MusicRequest(archive, group, version, checksum)
} else {
super.request(ctx, archive, group, version, checksum)
}
}
private companion object {
private val logger = InlineLogger()
private const val MUSIC_ARCHIVE = 40
}
}

@ -0,0 +1,22 @@
package org.openrs2.archive.cache
import io.netty.channel.Channel
import io.netty.channel.ChannelInitializer
import io.netty.handler.timeout.ReadTimeoutHandler
import org.openrs2.archive.cache.nxt.ClientOutOfDateCodec
import org.openrs2.archive.cache.nxt.InitJs5RemoteConnectionCodec
import org.openrs2.archive.cache.nxt.Js5OkCodec
import org.openrs2.protocol.Protocol
import org.openrs2.protocol.Rs2Decoder
import org.openrs2.protocol.Rs2Encoder
public class NxtJs5ChannelInitializer(private val handler: NxtJs5ChannelHandler) : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) {
ch.pipeline().addLast(
ReadTimeoutHandler(30),
Rs2Encoder(Protocol(InitJs5RemoteConnectionCodec)),
Rs2Decoder(Protocol(Js5OkCodec, ClientOutOfDateCodec))
)
ch.pipeline().addLast("handler", handler)
}
}

@ -0,0 +1,76 @@
package org.openrs2.archive.cache
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPipeline
import org.openrs2.cache.MasterIndexFormat
import org.openrs2.protocol.Rs2Decoder
import org.openrs2.protocol.Rs2Encoder
import org.openrs2.protocol.js5.downstream.Js5LoginResponse
import org.openrs2.protocol.js5.downstream.Js5Response
import org.openrs2.protocol.js5.downstream.Js5ResponseDecoder
import org.openrs2.protocol.js5.downstream.XorDecoder
import org.openrs2.protocol.js5.upstream.Js5Request
import org.openrs2.protocol.js5.upstream.Js5RequestEncoder
import org.openrs2.protocol.login.upstream.LoginRequest
import kotlin.coroutines.Continuation
public class OsrsJs5ChannelHandler(
bootstrap: Bootstrap,
scopeId: Int,
gameId: Int,
hostname: String,
port: Int,
build: Int,
lastMasterIndexId: Int?,
continuation: Continuation<Unit>,
importer: CacheImporter
) : Js5ChannelHandler(
bootstrap,
scopeId,
gameId,
hostname,
port,
build,
null,
lastMasterIndexId,
continuation,
importer,
MasterIndexFormat.VERSIONED,
maxInFlightRequests = 200
) {
override fun createInitMessage(): Any {
return LoginRequest.InitJs5RemoteConnection(buildMajor)
}
override fun createRequestMessage(prefetch: Boolean, archive: Int, group: Int): Any {
return Js5Request.Group(prefetch, archive, group)
}
override fun createConnectedMessage(): Any? {
return null
}
override fun configurePipeline(pipeline: ChannelPipeline) {
pipeline.addBefore("handler", null, Js5RequestEncoder)
pipeline.addBefore("handler", null, XorDecoder())
pipeline.addBefore("handler", null, Js5ResponseDecoder())
pipeline.remove(Rs2Encoder::class.java)
pipeline.remove(Rs2Decoder::class.java)
}
override fun incrementVersion() {
buildMajor++
}
override fun channelRead0(ctx: ChannelHandlerContext, msg: Any) {
when (msg) {
is Js5LoginResponse.Ok -> handleOk(ctx)
is Js5LoginResponse.ClientOutOfDate -> handleClientOutOfDate(ctx)
is Js5LoginResponse -> throw Exception("Invalid response: $msg")
is Js5Response -> handleResponse(ctx, msg.prefetch, msg.archive, msg.group, msg.data)
else -> throw Exception("Unknown message type: ${msg.javaClass.name}")
}
}
}

@ -0,0 +1,22 @@
package org.openrs2.archive.cache
import io.netty.channel.Channel
import io.netty.channel.ChannelInitializer
import io.netty.handler.timeout.ReadTimeoutHandler
import org.openrs2.protocol.Protocol
import org.openrs2.protocol.Rs2Decoder
import org.openrs2.protocol.Rs2Encoder
import org.openrs2.protocol.js5.downstream.Js5ClientOutOfDateCodec
import org.openrs2.protocol.js5.downstream.Js5OkCodec
import org.openrs2.protocol.login.upstream.InitJs5RemoteConnectionCodec
public class OsrsJs5ChannelInitializer(private val handler: OsrsJs5ChannelHandler) : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) {
ch.pipeline().addLast(
ReadTimeoutHandler(30),
Rs2Encoder(Protocol(InitJs5RemoteConnectionCodec())),
Rs2Decoder(Protocol(Js5OkCodec(), Js5ClientOutOfDateCodec()))
)
ch.pipeline().addLast("handler", handler)
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.cache
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class RefreshViewsCommand : CliktCommand(name = "refresh-views") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(CacheImporter::class.java)
importer.refreshViews()
}
}
}

@ -0,0 +1,149 @@
package org.openrs2.archive.cache.finder
import com.github.michaelbull.logging.InlineLogger
import com.google.common.io.ByteStreams
import com.google.common.io.LittleEndianDataInputStream
import org.openrs2.util.charset.Cp1252Charset
import java.io.Closeable
import java.io.EOFException
import java.io.IOException
import java.io.InputStream
import java.io.PushbackInputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributeView
import java.nio.file.attribute.FileTime
import java.time.Instant
public class CacheFinderExtractor(
input: InputStream
) : Closeable {
private val pushbackInput = PushbackInputStream(input)
private val input = LittleEndianDataInputStream(pushbackInput)
private fun readTimestamp(): FileTime {
val lo = input.readInt().toLong() and 0xFFFFFFFF
val hi = input.readInt().toLong() and 0xFFFFFFFF
val seconds = (((hi shl 32) or lo) / 10_000_000) - FILETIME_TO_UNIX_EPOCH
return FileTime.from(Instant.ofEpochSecond(seconds, lo))
}
private fun readName(): String {
val bytes = ByteArray(MAX_PATH)
input.readFully(bytes)
var len = bytes.size
for ((i, b) in bytes.withIndex()) {
if (b.toInt() == 0) {
len = i
break
}
}
return String(bytes, 0, len, Cp1252Charset)
}
private fun peekUnsignedByte(): Int {
val n = pushbackInput.read()
pushbackInput.unread(n)
return n
}
public fun extract(destination: Path) {
val newVersion = peekUnsignedByte() == 0xFE
if (newVersion) {
val signature = input.readInt()
if (signature != 0x435352FE) {
throw IOException("Invalid signature")
}
}
var readDirectoryPath = true
var number = 0
var directorySuffix: String? = null
while (true) {
if (newVersion && readDirectoryPath) {
val len = try {
input.readInt()
} catch (ex: EOFException) {
break
}
val bytes = ByteArray(len)
input.readFully(bytes)
val path = String(bytes, Cp1252Charset)
logger.info { "Extracting $path" }
readDirectoryPath = false
directorySuffix = path.substring(path.lastIndexOf('\\') + 1)
.replace(INVALID_CHARS, "_")
continue
}
if (peekUnsignedByte() == 0xFF) {
input.skipBytes(1)
readDirectoryPath = true
number++
continue
}
val attributes = try {
input.readInt()
} catch (ex: EOFException) {
break
}
val btime = readTimestamp()
val atime = readTimestamp()
val mtime = readTimestamp()
val sizeHi = input.readInt().toLong() and 0xFFFFFFFF
val sizeLo = input.readInt().toLong() and 0xFFFFFFFF
val size = (sizeHi shl 32) or sizeLo
input.skipBytes(8) // reserved
val name = readName()
input.skipBytes(14) // alternate name
input.skipBytes(2) // padding
val dir = if (directorySuffix != null) {
destination.resolve("cache${number}_$directorySuffix")
} else {
destination.resolve("cache$number")
}
Files.createDirectories(dir)
if ((attributes and FILE_ATTRIBUTE_DIRECTORY) == 0) {
val file = dir.resolve(name)
Files.newOutputStream(file).use { output ->
ByteStreams.copy(ByteStreams.limit(input, size), output)
}
val view = Files.getFileAttributeView(file, BasicFileAttributeView::class.java)
view.setTimes(mtime, atime, btime)
}
}
}
override fun close() {
input.close()
}
private companion object {
private const val FILETIME_TO_UNIX_EPOCH: Long = 11644473600
private const val MAX_PATH = 260
private const val FILE_ATTRIBUTE_DIRECTORY = 0x10
private val INVALID_CHARS = Regex("[^A-Za-z0-9-]")
private val logger = InlineLogger()
}
}

@ -0,0 +1,25 @@
package org.openrs2.archive.cache.finder
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.default
import com.github.ajalt.clikt.parameters.types.inputStream
import com.github.ajalt.clikt.parameters.types.path
import java.nio.file.Path
public class ExtractCommand : CliktCommand(name = "extract") {
private val input by argument().inputStream()
private val output by argument().path(
mustExist = false,
canBeFile = false,
canBeDir = true,
mustBeReadable = true,
mustBeWritable = true
).default(Path.of("."))
override fun run() {
CacheFinderExtractor(input).use { extractor ->
extractor.extract(output)
}
}
}

@ -0,0 +1,8 @@
package org.openrs2.archive.cache.nxt
import org.openrs2.protocol.EmptyPacketCodec
public object ClientOutOfDateCodec : EmptyPacketCodec<LoginResponse.ClientOutOfDate>(
opcode = 6,
packet = LoginResponse.ClientOutOfDate
)

@ -0,0 +1,10 @@
package org.openrs2.archive.cache.nxt
import org.openrs2.protocol.Packet
public data class InitJs5RemoteConnection(
public val buildMajor: Int,
public val buildMinor: Int,
public val token: String,
public val language: Int
) : Packet

@ -0,0 +1,27 @@
package org.openrs2.archive.cache.nxt
import io.netty.buffer.ByteBuf
import org.openrs2.buffer.readString
import org.openrs2.buffer.writeString
import org.openrs2.crypto.StreamCipher
import org.openrs2.protocol.VariableBytePacketCodec
public object InitJs5RemoteConnectionCodec : VariableBytePacketCodec<InitJs5RemoteConnection>(
type = InitJs5RemoteConnection::class.java,
opcode = 15
) {
override fun decode(input: ByteBuf, cipher: StreamCipher): InitJs5RemoteConnection {
val buildMajor = input.readInt()
val buildMinor = input.readInt()
val token = input.readString()
val language = input.readUnsignedByte().toInt()
return InitJs5RemoteConnection(buildMajor, buildMinor, token, language)
}
override fun encode(input: InitJs5RemoteConnection, output: ByteBuf, cipher: StreamCipher) {
output.writeInt(input.buildMajor)
output.writeInt(input.buildMinor)
output.writeString(input.token)
output.writeByte(input.language)
}
}

@ -0,0 +1,8 @@
package org.openrs2.archive.cache.nxt
import org.openrs2.protocol.EmptyPacketCodec
public object Js5OkCodec : EmptyPacketCodec<LoginResponse.Js5Ok>(
opcode = 0,
packet = LoginResponse.Js5Ok
)

@ -0,0 +1,14 @@
package org.openrs2.archive.cache.nxt
public sealed class Js5Request {
public data class Group(
public val prefetch: Boolean,
public val archive: Int,
public val group: Int,
public val build: Int
) : Js5Request()
public data class Connected(
public val build: Int
) : Js5Request()
}

@ -0,0 +1,37 @@
package org.openrs2.archive.cache.nxt
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.MessageToByteEncoder
@ChannelHandler.Sharable
public object Js5RequestEncoder : MessageToByteEncoder<Js5Request>(Js5Request::class.java) {
override fun encode(ctx: ChannelHandlerContext, msg: Js5Request, out: ByteBuf) {
when (msg) {
is Js5Request.Group -> {
out.writeByte(if (msg.prefetch) 32 else 33)
out.writeByte(msg.archive)
out.writeInt(msg.group)
out.writeShort(msg.build)
out.writeShort(0)
}
is Js5Request.Connected -> {
out.writeByte(6)
out.writeMedium(5)
out.writeShort(0)
out.writeShort(msg.build)
out.writeShort(0)
}
}
}
override fun allocateBuffer(ctx: ChannelHandlerContext, msg: Js5Request, preferDirect: Boolean): ByteBuf {
return if (preferDirect) {
ctx.alloc().ioBuffer(10, 10)
} else {
ctx.alloc().heapBuffer(10, 10)
}
}
}

@ -0,0 +1,11 @@
package org.openrs2.archive.cache.nxt
import io.netty.buffer.ByteBuf
import io.netty.buffer.DefaultByteBufHolder
public data class Js5Response(
public val prefetch: Boolean,
public val archive: Int,
public val group: Int,
public val data: ByteBuf
) : DefaultByteBufHolder(data)

@ -0,0 +1,121 @@
package org.openrs2.archive.cache.nxt
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.ByteToMessageDecoder
import io.netty.handler.codec.DecoderException
import kotlin.math.min
public class Js5ResponseDecoder : ByteToMessageDecoder() {
private data class Request(val prefetch: Boolean, val archive: Int, val group: Int)
private enum class State {
READ_HEADER,
READ_LEN,
READ_DATA
}
private var state = State.READ_HEADER
private val buffers = mutableMapOf<Request, ByteBuf>()
private var request: Request? = null
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
if (state == State.READ_HEADER) {
if (input.readableBytes() < 5) {
return
}
val prefetch: Boolean
val archive = input.readUnsignedByte().toInt()
var group = input.readInt()
if (group and 0x80000000.toInt() != 0) {
prefetch = true
group = group and 0x7FFFFFFF
} else {
prefetch = false
}
request = Request(prefetch, archive, group)
state = if (buffers.containsKey(request)) {
State.READ_DATA
} else {
State.READ_LEN
}
}
if (state == State.READ_LEN) {
if (input.readableBytes() < 5) {
return
}
val type = input.readUnsignedByte().toInt()
val len = input.readInt()
if (len < 0) {
throw DecoderException("Length is negative: $len")
}
val totalLen = if (type == 0) {
len + 5
} else {
len + 9
}
if (totalLen < 0) {
throw DecoderException("Total length exceeds maximum ByteBuf size")
}
val data = ctx.alloc().buffer(totalLen, totalLen)
data.writeByte(type)
data.writeInt(len)
buffers[request!!] = data
state = State.READ_DATA
}
if (state == State.READ_DATA) {
val data = buffers[request!!]!!
var blockLen = if (data.writerIndex() == 5) {
102400 - 10
} else {
102400 - 5
}
blockLen = min(blockLen, data.writableBytes())
if (input.readableBytes() < blockLen) {
return
}
data.writeBytes(input, blockLen)
if (!data.isWritable) {
out += Js5Response(request!!.prefetch, request!!.archive, request!!.group, data)
buffers.remove(request!!)
request = null
}
state = State.READ_HEADER
}
}
override fun channelInactive(ctx: ChannelHandlerContext) {
super.channelInactive(ctx)
reset()
}
override fun handlerRemoved0(ctx: ChannelHandlerContext?) {
reset()
}
private fun reset() {
buffers.values.forEach(ByteBuf::release)
buffers.clear()
state = State.READ_HEADER
}
}

@ -0,0 +1,8 @@
package org.openrs2.archive.cache.nxt
import org.openrs2.protocol.Packet
public sealed class LoginResponse : Packet {
public object Js5Ok : LoginResponse()
public object ClientOutOfDate : LoginResponse()
}

@ -0,0 +1,32 @@
package org.openrs2.archive.cache.nxt
import io.netty.buffer.ByteBuf
import kotlinx.coroutines.future.await
import org.openrs2.buffer.ByteBufBodyHandler
import org.openrs2.buffer.use
import org.openrs2.http.checkStatusCode
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.time.Duration
public class MusicStreamClient(
private val client: HttpClient,
private val byteBufBodyHandler: ByteBufBodyHandler,
private val origin: String
) {
public suspend fun request(archive: Int, group: Int, version: Int, checksum: Int, build: Int): ByteBuf {
val uri = URI("$origin/ms?m=0&a=$archive&k=$build&g=$group&c=$checksum&v=$version")
val request = HttpRequest.newBuilder(uri)
.GET()
.timeout(Duration.ofSeconds(30))
.build()
val response = client.sendAsync(request, byteBufBodyHandler).await()
response.body().use { buf ->
response.checkStatusCode()
return buf.retain()
}
}
}

@ -0,0 +1,11 @@
package org.openrs2.archive.client
public enum class Architecture {
INDEPENDENT,
UNIVERSAL,
X86,
AMD64,
POWERPC,
SPARC,
SPARCV9
}

@ -0,0 +1,35 @@
package org.openrs2.archive.client
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufUtil
import org.openrs2.archive.cache.CacheExporter
import org.openrs2.archive.cache.CacheImporter
import java.time.Instant
public class Artifact(
data: ByteBuf,
public val game: String,
public val environment: String,
public val build: CacheExporter.Build?,
public val timestamp: Instant?,
public val type: ArtifactType,
public val format: ArtifactFormat,
public val os: OperatingSystem,
public val arch: Architecture,
public val jvm: Jvm,
public val links: List<ArtifactLink>
) : CacheImporter.Blob(data)
public data class ArtifactLink(
val type: ArtifactType,
val format: ArtifactFormat,
val os: OperatingSystem,
val arch: Architecture,
val jvm: Jvm,
val crc32: Int?,
val sha1: ByteArray,
val size: Int?
) {
public val sha1Hex: String
get() = ByteBufUtil.hexDump(sha1)
}

@ -0,0 +1,46 @@
package org.openrs2.archive.client
import io.ktor.http.ContentType
public enum class ArtifactFormat {
CAB,
JAR,
NATIVE,
PACK200,
PACKCLASS;
public fun getPrefix(os: OperatingSystem): String {
return when (this) {
NATIVE -> os.getPrefix()
else -> ""
}
}
public fun getExtension(os: OperatingSystem): String {
return when (this) {
CAB -> "cab"
JAR -> "jar"
NATIVE -> os.getExtension()
PACK200 -> "pack200"
PACKCLASS -> "js5"
}
}
public fun getContentType(os: OperatingSystem): ContentType {
return when (this) {
CAB -> CAB_MIME_TYPE
JAR -> JAR_MIME_TYPE
NATIVE -> os.getContentType()
PACK200, PACKCLASS -> ContentType.Application.OctetStream
}
}
public fun isJar(): Boolean {
return this != NATIVE
}
private companion object {
private val CAB_MIME_TYPE = ContentType("application", "vnd.ms-cab-compressed")
private val JAR_MIME_TYPE = ContentType("application", "java-archive")
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.client
public enum class ArtifactType {
BROWSERCONTROL,
CLIENT,
CLIENT_GL,
GLUEGEN_RT,
JAGGL,
JAGGL_DRI,
JAGMISC,
JOGL,
JOGL_AWT,
LOADER,
LOADER_GL,
UNPACKCLASS
}

@ -0,0 +1,14 @@
package org.openrs2.archive.client
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
public class ClientCommand : NoOpCliktCommand(name = "client") {
init {
subcommands(
ExportCommand(),
ImportCommand(),
RefreshCommand()
)
}
}

@ -0,0 +1,455 @@
package org.openrs2.archive.client
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufUtil
import io.netty.buffer.DefaultByteBufHolder
import io.netty.buffer.Unpooled
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.openrs2.archive.cache.CacheExporter
import org.openrs2.db.Database
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
@Singleton
public class ClientExporter @Inject constructor(
private val database: Database
) {
public data class ArtifactSummary(
public val id: Long,
public val game: String,
public val environment: String,
public val build: CacheExporter.Build?,
public val timestamp: Instant?,
public val type: ArtifactType,
public val format: ArtifactFormat,
public val os: OperatingSystem,
public val arch: Architecture,
public val jvm: Jvm,
public val size: Int
) {
public val name: String
get() {
val builder = StringBuilder()
builder.append(format.getPrefix(os))
when (type) {
ArtifactType.CLIENT -> builder.append(game)
ArtifactType.CLIENT_GL -> builder.append("${game}_gl")
ArtifactType.GLUEGEN_RT -> builder.append("gluegen-rt")
else -> builder.append(type.name.lowercase())
}
if (jvm == Jvm.MICROSOFT) {
builder.append("ms")
}
if (os != OperatingSystem.INDEPENDENT) {
builder.append('-')
builder.append(os.name.lowercase())
}
if (arch != Architecture.INDEPENDENT) {
builder.append('-')
builder.append(arch.name.lowercase())
}
if (build != null) {
builder.append("-b")
builder.append(build)
}
if (timestamp != null) {
builder.append('-')
builder.append(
timestamp
.atOffset(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"))
)
}
builder.append("-openrs2#")
builder.append(id)
builder.append('.')
builder.append(format.getExtension(os))
return builder.toString()
}
}
public data class ArtifactSource(
public val name: String?,
public val description: String?,
public val url: String?
)
public data class ArtifactLinkExport(
public val id: Long?,
public val build: CacheExporter.Build?,
public val timestamp: Instant?,
public val link: ArtifactLink
)
public class Artifact(
public val summary: ArtifactSummary,
public val crc32: Int,
public val sha1: ByteArray,
public val sources: List<ArtifactSource>,
public val links: List<ArtifactLinkExport>
) {
public val sha1Hex: String
get() = ByteBufUtil.hexDump(sha1)
}
public class ArtifactExport(
public val summary: ArtifactSummary,
buf: ByteBuf
) : DefaultByteBufHolder(buf)
public suspend fun list(): List<ArtifactSummary> {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT
a.blob_id,
g.name,
e.name,
a.build_major,
a.build_minor,
a.timestamp,
a.type,
a.format,
a.os,
a.arch,
a.jvm,
length(b.data) AS size
FROM artifacts a
JOIN blobs b ON b.id = a.blob_id
JOIN games g ON g.id = a.game_id
JOIN environments e ON e.id = a.environment_id
ORDER BY a.build_major ASC, a.timestamp ASC, a.type ASC, a.format ASC, a.os ASC, a.arch ASC, a.jvm ASC
""".trimIndent()
).use { stmt ->
stmt.executeQuery().use { rows ->
val artifacts = mutableListOf<ArtifactSummary>()
while (rows.next()) {
val id = rows.getLong(1)
val game = rows.getString(2)
val environment = rows.getString(3)
var buildMajor: Int? = rows.getInt(4)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(5)
if (rows.wasNull()) {
buildMinor = null
}
val build = if (buildMajor != null) {
CacheExporter.Build(buildMajor, buildMinor)
} else {
null
}
val timestamp = rows.getTimestamp(6)?.toInstant()
val type = ArtifactType.valueOf(rows.getString(7).uppercase())
val format = ArtifactFormat.valueOf(rows.getString(8).uppercase())
val os = OperatingSystem.valueOf(rows.getString(9).uppercase())
val arch = Architecture.valueOf(rows.getString(10).uppercase())
val jvm = Jvm.valueOf(rows.getString(11).uppercase())
val size = rows.getInt(12)
artifacts += ArtifactSummary(
id,
game,
environment,
build,
timestamp,
type,
format,
os,
arch,
jvm,
size
)
}
return@execute artifacts
}
}
}
}
public suspend fun get(id: Long): Artifact? {
return database.execute { connection ->
val sources = mutableListOf<ArtifactSource>()
val links = mutableListOf<ArtifactLinkExport>()
connection.prepareStatement(
"""
SELECT DISTINCT name, description, url
FROM artifact_sources
WHERE blob_id = ?
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val name = rows.getString(1)
val description = rows.getString(2)
val url = rows.getString(3)
sources += ArtifactSource(name, description, url)
}
}
}
connection.prepareStatement(
"""
SELECT
a.blob_id,
a.build_major,
a.build_minor,
a.timestamp,
l.type,
l.format,
l.os,
l.arch,
l.jvm,
COALESCE(l.crc32, b.crc32),
l.sha1,
COALESCE(l.size, length(b.data))
FROM artifact_links l
LEFT JOIN blobs b ON b.sha1 = l.sha1
LEFT JOIN artifacts a ON a.blob_id = b.id
WHERE l.blob_id = ?
ORDER BY l.type, l.format, l.os, l.arch, l.jvm
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
var linkId: Long? = rows.getLong(1)
if (rows.wasNull()) {
linkId = null
}
var buildMajor: Int? = rows.getInt(2)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(3)
if (rows.wasNull()) {
buildMinor = null
}
val build = if (buildMajor != null) {
CacheExporter.Build(buildMajor, buildMinor)
} else {
null
}
val timestamp = rows.getTimestamp(4)?.toInstant()
val type = ArtifactType.valueOf(rows.getString(5).uppercase())
val format = ArtifactFormat.valueOf(rows.getString(6).uppercase())
val os = OperatingSystem.valueOf(rows.getString(7).uppercase())
val arch = Architecture.valueOf(rows.getString(8).uppercase())
val jvm = Jvm.valueOf(rows.getString(9).uppercase())
var crc32: Int? = rows.getInt(10)
if (rows.wasNull()) {
crc32 = null
}
val sha1 = rows.getBytes(11)
var size: Int? = rows.getInt(12)
if (rows.wasNull()) {
size = null
}
links += ArtifactLinkExport(
linkId,
build,
timestamp,
ArtifactLink(
type,
format,
os,
arch,
jvm,
crc32,
sha1,
size
)
)
}
}
}
connection.prepareStatement(
"""
SELECT
g.name,
e.name,
a.build_major,
a.build_minor,
a.timestamp,
a.type,
a.format,
a.os,
a.arch,
a.jvm,
length(b.data) AS size,
b.crc32,
b.sha1
FROM artifacts a
JOIN games g ON g.id = a.game_id
JOIN environments e ON e.id = a.environment_id
JOIN blobs b ON b.id = a.blob_id
WHERE a.blob_id = ?
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val game = rows.getString(1)
val environment = rows.getString(2)
var buildMajor: Int? = rows.getInt(3)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(4)
if (rows.wasNull()) {
buildMinor = null
}
val build = if (buildMajor != null) {
CacheExporter.Build(buildMajor!!, buildMinor)
} else {
null
}
val timestamp = rows.getTimestamp(5)?.toInstant()
val type = ArtifactType.valueOf(rows.getString(6).uppercase())
val format = ArtifactFormat.valueOf(rows.getString(7).uppercase())
val os = OperatingSystem.valueOf(rows.getString(8).uppercase())
val arch = Architecture.valueOf(rows.getString(9).uppercase())
val jvm = Jvm.valueOf(rows.getString(10).uppercase())
val size = rows.getInt(11)
val crc32 = rows.getInt(12)
val sha1 = rows.getBytes(13)
return@execute Artifact(
ArtifactSummary(
id,
game,
environment,
build,
timestamp,
type,
format,
os,
arch,
jvm,
size
), crc32, sha1, sources, links
)
}
}
}
}
public suspend fun export(id: Long): ArtifactExport? {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT
g.name,
e.name,
a.build_major,
a.build_minor,
a.timestamp,
a.type,
a.format,
a.os,
a.arch,
a.jvm,
b.data
FROM artifacts a
JOIN games g ON g.id = a.game_id
JOIN environments e ON e.id = a.environment_id
JOIN blobs b ON b.id = a.blob_id
WHERE a.blob_id = ?
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val game = rows.getString(1)
val environment = rows.getString(2)
var buildMajor: Int? = rows.getInt(3)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(4)
if (rows.wasNull()) {
buildMinor = null
}
val build = if (buildMajor != null) {
CacheExporter.Build(buildMajor, buildMinor)
} else {
null
}
val timestamp = rows.getTimestamp(5)?.toInstant()
val type = ArtifactType.valueOf(rows.getString(6).uppercase())
val format = ArtifactFormat.valueOf(rows.getString(7).uppercase())
val os = OperatingSystem.valueOf(rows.getString(8).uppercase())
val arch = Architecture.valueOf(rows.getString(9).uppercase())
val jvm = Jvm.valueOf(rows.getString(10).uppercase())
val buf = Unpooled.wrappedBuffer(rows.getBytes(11))
val size = buf.readableBytes()
return@execute ArtifactExport(
ArtifactSummary(
id,
game,
environment,
build,
timestamp,
type,
format,
os,
arch,
jvm,
size
), buf
)
}
}
}
}
}

@ -0,0 +1,997 @@
package org.openrs2.archive.client
import com.github.michaelbull.logging.InlineLogger
import com.kichik.pecoff4j.PE
import com.kichik.pecoff4j.constant.MachineType
import com.kichik.pecoff4j.io.PEParser
import dorkbox.cabParser.CabParser
import dorkbox.cabParser.CabStreamSaver
import dorkbox.cabParser.structure.CabFileEntry
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufAllocator
import io.netty.buffer.ByteBufInputStream
import io.netty.buffer.ByteBufOutputStream
import io.netty.buffer.Unpooled
import io.netty.util.ByteProcessor
import jakarta.inject.Inject
import jakarta.inject.Singleton
import net.fornwall.jelf.ElfFile
import net.fornwall.jelf.ElfSymbol
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.TypeInsnNode
import org.openrs2.archive.cache.CacheExporter
import org.openrs2.archive.cache.CacheImporter
import org.openrs2.asm.InsnMatcher
import org.openrs2.asm.classpath.Library
import org.openrs2.asm.getArgumentExpressions
import org.openrs2.asm.hasCode
import org.openrs2.asm.intConstant
import org.openrs2.asm.io.CabLibraryReader
import org.openrs2.asm.io.JarLibraryReader
import org.openrs2.asm.io.LibraryReader
import org.openrs2.asm.io.Pack200LibraryReader
import org.openrs2.asm.io.PackClassLibraryReader
import org.openrs2.asm.nextReal
import org.openrs2.buffer.use
import org.openrs2.compress.gzip.Gzip
import org.openrs2.db.Database
import org.openrs2.util.io.entries
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.sql.Connection
import java.sql.Types
import java.time.Instant
import java.time.LocalDate
import java.time.Month
import java.time.ZoneOffset
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.jar.Pack200
import kotlin.io.path.getLastModifiedTime
@Singleton
public class ClientImporter @Inject constructor(
private val database: Database,
private val alloc: ByteBufAllocator,
private val packClassLibraryReader: PackClassLibraryReader,
private val importer: CacheImporter
) {
public suspend fun import(
paths: Iterable<Path>,
name: String?,
description: String?,
url: String?,
skipErrors: Boolean
) {
alloc.buffer().use { buf ->
for (path in paths) {
buf.clear()
Files.newInputStream(path).use { input ->
ByteBufOutputStream(buf).use { output ->
input.copyTo(output)
}
}
logger.info { "Importing $path" }
try {
import(
parse(buf),
name,
description,
url,
path.fileName.toString(),
path.getLastModifiedTime().toInstant()
)
} catch (t: Throwable) {
if (skipErrors) {
logger.warn(t) { "Failed to import $path" }
continue
}
throw t
}
}
}
}
public suspend fun import(
artifact: Artifact,
name: String?,
description: String?,
url: String?,
fileName: String,
timestamp: Instant
) {
database.execute { connection ->
importer.prepare(connection)
val id = import(connection, artifact)
connection.prepareStatement(
"""
INSERT INTO artifact_sources (blob_id, name, description, url, file_name, timestamp)
VALUES (?, ?, ?, ?, ?, ?)
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.setString(2, name)
stmt.setString(3, description)
stmt.setString(4, url)
stmt.setString(5, fileName)
stmt.setObject(6, timestamp.atOffset(ZoneOffset.UTC), Types.TIMESTAMP_WITH_TIMEZONE)
stmt.execute()
}
}
}
private fun import(connection: Connection, artifact: Artifact): Long {
val id = importer.addBlob(connection, artifact)
val gameId = connection.prepareStatement(
"""
SELECT id
FROM games
WHERE name = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, artifact.game)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
throw IllegalArgumentException()
}
rows.getInt(1)
}
}
val environmentId = connection.prepareStatement(
"""
SELECT id
FROM environments
WHERE name = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, artifact.environment)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
throw IllegalArgumentException()
}
rows.getInt(1)
}
}
connection.prepareStatement(
"""
INSERT INTO artifacts (blob_id, game_id, environment_id, build_major, build_minor, timestamp, type, format, os, arch, jvm)
VALUES (?, ?, ?, ?, ?, ?, ?::artifact_type, ?::artifact_format, ?::os, ?::arch, ?::jvm)
ON CONFLICT (blob_id) DO UPDATE SET
game_id = EXCLUDED.game_id,
environment_id = EXCLUDED.environment_id,
build_major = EXCLUDED.build_major,
build_minor = EXCLUDED.build_minor,
timestamp = EXCLUDED.timestamp,
type = EXCLUDED.type,
format = EXCLUDED.format,
os = EXCLUDED.os,
arch = EXCLUDED.arch,
jvm = EXCLUDED.jvm
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.setInt(2, gameId)
stmt.setInt(3, environmentId)
stmt.setObject(4, artifact.build?.major, Types.INTEGER)
stmt.setObject(5, artifact.build?.minor, Types.INTEGER)
stmt.setObject(6, artifact.timestamp?.atOffset(ZoneOffset.UTC), Types.TIMESTAMP_WITH_TIMEZONE)
stmt.setString(7, artifact.type.name.lowercase())
stmt.setString(8, artifact.format.name.lowercase())
stmt.setString(9, artifact.os.name.lowercase())
stmt.setString(10, artifact.arch.name.lowercase())
stmt.setString(11, artifact.jvm.name.lowercase())
stmt.execute()
}
connection.prepareStatement(
"""
DELETE FROM artifact_links
WHERE blob_id = ?
""".trimIndent()
).use { stmt ->
stmt.setLong(1, id)
stmt.execute()
}
connection.prepareStatement(
"""
INSERT INTO artifact_links (blob_id, type, format, os, arch, jvm, sha1, crc32, size)
VALUES (?, ?::artifact_type, ?::artifact_format, ?::os, ?::arch, ?::jvm, ?, ?, ?)
""".trimIndent()
).use { stmt ->
for (link in artifact.links) {
stmt.setLong(1, id)
stmt.setString(2, link.type.name.lowercase())
stmt.setString(3, link.format.name.lowercase())
stmt.setString(4, link.os.name.lowercase())
stmt.setString(5, link.arch.name.lowercase())
stmt.setString(6, link.jvm.name.lowercase())
stmt.setBytes(7, link.sha1)
stmt.setObject(8, link.crc32, Types.INTEGER)
stmt.setObject(9, link.size, Types.INTEGER)
stmt.addBatch()
}
stmt.executeBatch()
}
return id
}
public suspend fun refresh() {
data class Blob(val id: Long, val bytes: ByteArray)
database.execute { connection ->
importer.prepare(connection)
var lastId: Long? = null
val blobs = mutableListOf<Blob>()
while (true) {
blobs.clear()
connection.prepareStatement(
"""
SELECT a.blob_id, b.data
FROM artifacts a
JOIN blobs b ON b.id = a.blob_id
WHERE ? IS NULL OR a.blob_id > ?
ORDER BY a.blob_id ASC
LIMIT 1024
""".trimIndent()
).use { stmt ->
stmt.setObject(1, lastId, Types.BIGINT)
stmt.setObject(2, lastId, Types.BIGINT)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val id = rows.getLong(1)
lastId = id
blobs += Blob(id, rows.getBytes(2))
}
}
}
if (blobs.isEmpty()) {
return@execute
}
for (blob in blobs) {
logger.info { "Refreshing artifact ${blob.id}" }
Unpooled.wrappedBuffer(blob.bytes).use { buf ->
import(connection, parse(buf))
}
}
}
}
}
private fun parse(buf: ByteBuf): Artifact {
return if (buf.hasPrefix(JAR)) {
parseJar(buf)
} else if (buf.hasPrefix(PACK200)) {
parsePack200(buf)
} else if (buf.hasPrefix(CAB)) {
parseCab(buf)
} else if (
buf.hasPrefix(PACKCLASS_UNCOMPRESSED) ||
buf.hasPrefix(PACKCLASS_BZIP2) ||
buf.hasPrefix(PACKCLASS_GZIP)
) {
parseLibrary(buf, packClassLibraryReader, ArtifactFormat.PACKCLASS)
} else if (buf.hasPrefix(ELF)) {
parseElf(buf)
} else if (buf.hasPrefix(PE)) {
parsePe(buf)
} else if (
buf.hasPrefix(MACHO32BE) ||
buf.hasPrefix(MACHO32LE) ||
buf.hasPrefix(MACHO64BE) ||
buf.hasPrefix(MACHO64LE) ||
buf.hasPrefix(MACHO_UNIVERSAL)
) {
parseMachO(buf)
} else {
throw IllegalArgumentException()
}
}
private fun parseElf(buf: ByteBuf): Artifact {
val elf = ElfFile.from(ByteBufInputStream(buf.slice()))
val arch = when (elf.e_machine.toInt()) {
ElfFile.ARCH_i386 -> Architecture.X86
ElfFile.ARCH_X86_64 -> Architecture.AMD64
ElfFile.ARCH_SPARC -> Architecture.SPARC
ARCH_SPARCV9 -> Architecture.SPARCV9
else -> throw IllegalArgumentException()
}
val comment = String(elf.firstSectionByName(".comment").data)
val os = if (comment.contains(SOLARIS_COMMENT)) {
OperatingSystem.SOLARIS
} else {
OperatingSystem.LINUX
}
val symbols = elf.dynamicSymbolTableSection ?: throw IllegalArgumentException()
val type = getArtifactType(symbols.symbols.asSequence().mapNotNull(ElfSymbol::getName))
return Artifact(
buf.retain(),
"shared",
"live",
null,
null,
type,
ArtifactFormat.NATIVE,
os,
arch,
Jvm.SUN,
emptyList()
)
}
private fun getArtifactType(symbols: Sequence<String>): ArtifactType {
for (symbol in symbols) {
var name = symbol
if (name.startsWith('_')) {
name = name.substring(1)
}
if (name.startsWith("Java_")) { // RNI methods don't have a Java_ prefix
name = name.substring("Java_".length)
}
if (name.startsWith("jaggl_X11_dri_")) {
return ArtifactType.JAGGL_DRI
} else if (name.startsWith("jaggl_opengl_")) {
return ArtifactType.JAGGL
} else if (name.startsWith("com_sun_opengl_impl_GLImpl_")) {
return ArtifactType.JOGL
} else if (name.startsWith("com_sun_opengl_impl_JAWT_")) {
return ArtifactType.JOGL_AWT
} else if (name.startsWith("com_sun_gluegen_runtime_")) {
return ArtifactType.GLUEGEN_RT
} else if (name.startsWith("jagex3_jagmisc_jagmisc_")) {
return ArtifactType.JAGMISC
} else if (name.startsWith("nativeadvert_browsercontrol_")) {
return ArtifactType.BROWSERCONTROL
}
}
throw IllegalArgumentException()
}
private fun parsePe(buf: ByteBuf): Artifact {
val pe = PEParser.parse(ByteBufInputStream(buf.slice()))
val arch = when (pe.coffHeader.machine) {
MachineType.IMAGE_FILE_MACHINE_I386 -> Architecture.X86
MachineType.IMAGE_FILE_MACHINE_AMD64 -> Architecture.AMD64
else -> throw IllegalArgumentException()
}
val symbols = parsePeExportNames(buf, pe).toSet()
val type = getArtifactType(symbols.asSequence())
val jvm = if (symbols.contains("RNIGetCompatibleVersion")) {
Jvm.MICROSOFT
} else {
Jvm.SUN
}
return Artifact(
buf.retain(),
"shared",
"live",
null,
Instant.ofEpochSecond(pe.coffHeader.timeDateStamp.toLong()),
type,
ArtifactFormat.NATIVE,
OperatingSystem.WINDOWS,
arch,
jvm,
emptyList()
)
}
private fun parsePeExportNames(buf: ByteBuf, pe: PE): Sequence<String> {
return sequence {
val exportTable = pe.imageData.exportTable
val namePointerTable =
pe.sectionTable.rvaConverter.convertVirtualAddressToRawDataPointer(exportTable.namePointerRVA.toInt())
for (i in 0 until exportTable.numberOfNamePointers.toInt()) {
val namePointerRva = buf.readerIndex() + buf.getIntLE(buf.readerIndex() + namePointerTable + 4 * i)
val namePointer = pe.sectionTable.rvaConverter.convertVirtualAddressToRawDataPointer(namePointerRva)
val end = buf.forEachByte(namePointer, buf.writerIndex() - namePointer, ByteProcessor.FIND_NUL)
require(end != -1) {
"Unterminated string"
}
yield(buf.toString(namePointer, end - namePointer, Charsets.US_ASCII))
}
}
}
private fun parseMachO(buf: ByteBuf): Artifact {
val (arch, symbols) = MachO.parse(buf.slice())
val type = getArtifactType(symbols.asSequence())
return Artifact(
buf.retain(),
"shared",
"live",
null,
null,
type,
ArtifactFormat.NATIVE,
OperatingSystem.MACOS,
arch,
Jvm.SUN,
emptyList()
)
}
private fun parseJar(buf: ByteBuf): Artifact {
val timestamp = getJarTimestamp(ByteBufInputStream(buf.slice()))
return parseLibrary(buf, JarLibraryReader, ArtifactFormat.JAR, timestamp)
}
private fun parsePack200(buf: ByteBuf): Artifact {
val timestamp = ByteArrayOutputStream().use { tempOutput ->
Gzip.createHeaderlessInputStream(ByteBufInputStream(buf.slice())).use { gzipInput ->
JarOutputStream(tempOutput).use { jarOutput ->
Pack200.newUnpacker().unpack(gzipInput, jarOutput)
}
}
getJarTimestamp(ByteArrayInputStream(tempOutput.toByteArray()))
}
return parseLibrary(buf, Pack200LibraryReader, ArtifactFormat.PACK200, timestamp)
}
private fun parseCab(buf: ByteBuf): Artifact {
val timestamp = getCabTimestamp(ByteBufInputStream(buf.slice()))
return parseLibrary(buf, CabLibraryReader, ArtifactFormat.CAB, timestamp)
}
private fun getJarTimestamp(input: InputStream): Instant? {
var timestamp: Instant? = null
JarInputStream(input).use { jar ->
for (entry in jar.entries) {
val t = entry.lastModifiedTime?.toInstant()
if (timestamp == null || (t != null && t < timestamp)) {
timestamp = t
}
}
}
return timestamp
}
private fun getCabTimestamp(input: InputStream): Instant? {
var timestamp: Instant? = null
CabParser(input, object : CabStreamSaver {
override fun closeOutputStream(outputStream: OutputStream, entry: CabFileEntry) {
// entry
}
override fun openOutputStream(entry: CabFileEntry): OutputStream {
val t = entry.date.toInstant()
if (timestamp == null || t < timestamp) {
timestamp = t
}
return OutputStream.nullOutputStream()
}
override fun saveReservedAreaData(data: ByteArray?, dataLength: Int): Boolean {
return false
}
}).extractStream()
return timestamp
}
private fun parseLibrary(
buf: ByteBuf,
reader: LibraryReader,
format: ArtifactFormat,
timestamp: Instant? = null
): Artifact {
val library = Library.read("client", ByteBufInputStream(buf.slice()), reader)
val game: String
val build: CacheExporter.Build?
val type: ArtifactType
val links: List<ArtifactLink>
val mudclient = library["mudclient"]
val client = library["client"]
val loader = library["loader"]
if (mudclient != null) {
game = "classic"
build = null // TODO(gpe): classic support
type = ArtifactType.CLIENT
links = emptyList()
} else if (client != null) {
game = "runescape"
build = parseClientBuild(library, client)
type = if (build != null && build.major < COMBINED_BUILD && isClientGl(library)) {
ArtifactType.CLIENT_GL
} else {
ArtifactType.CLIENT
}
links = emptyList()
} else if (loader != null) {
if (isLoaderClassic(loader)) {
game = "classic"
build = null // TODO(gpe): classic support
type = ArtifactType.LOADER
links = emptyList() // TODO(gpe): classic support
} else {
game = "runescape"
build = parseSignLinkBuild(library)
type = if (timestamp != null && timestamp < COMBINED_TIMESTAMP && isLoaderGl(library)) {
ArtifactType.LOADER_GL
} else {
ArtifactType.LOADER
}
links = parseLinks(library)
}
} else if (library.contains("mapview")) {
game = "mapview"
build = null
type = ArtifactType.CLIENT
links = emptyList()
} else if (library.contains("loginapplet")) {
game = "loginapplet"
build = null
type = ArtifactType.CLIENT
links = emptyList()
} else if (library.contains("passwordapp")) {
game = "passapplet"
build = null
type = ArtifactType.CLIENT
links = emptyList()
} else if (library.contains("jaggl/opengl")) {
game = "shared"
type = ArtifactType.JAGGL
build = null
links = emptyList()
} else if (library.contains("com/sun/opengl/impl/GLImpl")) {
game = "shared"
type = ArtifactType.JOGL
build = null
links = emptyList()
} else if (library.contains("unpackclass")) {
game = "shared"
type = ArtifactType.UNPACKCLASS
build = null
links = emptyList()
} else {
throw IllegalArgumentException()
}
return Artifact(
buf.retain(),
game,
"live",
build,
timestamp,
type,
format,
OperatingSystem.INDEPENDENT,
Architecture.INDEPENDENT,
Jvm.INDEPENDENT,
links
)
}
private fun isClientGl(library: Library): Boolean {
for (clazz in library) {
for (method in clazz.methods) {
if (!method.hasCode) {
continue
}
for (insn in method.instructions) {
if (insn is MethodInsnNode && insn.name == "glBegin") {
return true
}
}
}
}
return false
}
private fun isLoaderClassic(clazz: ClassNode): Boolean {
for (method in clazz.methods) {
if (!method.hasCode) {
continue
}
for (insn in method.instructions) {
if (insn is LdcInsnNode && insn.cst == "mudclient") {
return true
}
}
}
return false
}
private fun isLoaderGl(library: Library): Boolean {
for (clazz in library) {
for (method in clazz.methods) {
if (!method.hasCode || method.name != "<clinit>") {
continue
}
for (insn in method.instructions) {
if (insn !is LdcInsnNode) {
continue
}
if (insn.cst == "jaggl.dll" || insn.cst == "jogl.dll") {
return true
}
}
}
}
return false
}
private fun parseClientBuild(library: Library, clazz: ClassNode): CacheExporter.Build? {
for (method in clazz.methods) {
if (!method.hasCode || method.name != "main") {
continue
}
for (match in OLD_ENGINE_VERSION_MATCHER.match(method)) {
val ldc = match[0] as LdcInsnNode
if (ldc.cst != OLD_ENGINE_VERSION_STRING) {
continue
}
val version = match[2].intConstant
if (version != null) {
return CacheExporter.Build(version, null)
}
}
var betweenNewAndReturn = false
val candidates = mutableListOf<Int>()
for (insn in method.instructions) {
if (insn is TypeInsnNode && insn.desc == "client") {
betweenNewAndReturn = true
} else if (insn.opcode == Opcodes.RETURN) {
break
} else if (betweenNewAndReturn) {
val candidate = insn.intConstant
if (candidate != null && candidate in NEW_ENGINE_BUILDS) {
candidates += candidate
}
}
}
for (build in NEW_ENGINE_RESOLUTIONS) {
candidates -= build
}
val version = candidates.singleOrNull()
if (version != null) {
return CacheExporter.Build(version, null)
}
}
return parseSignLinkBuild(library)
}
private fun parseSignLinkBuild(library: Library): CacheExporter.Build? {
val clazz = library["sign/signlink"] ?: return null
for (field in clazz.fields) {
val value = field.value
if (field.name == "clientversion" && field.desc == "I" && value is Int) {
return CacheExporter.Build(value, null)
}
}
return null
}
private fun parseLinks(library: Library): List<ArtifactLink> {
val sig = library["sig"]
if (sig != null) {
var size: Int? = null
var sha1: ByteArray? = null
for (field in sig.fields) {
val value = field.value
if (field.name == "len" && field.desc == "I" && value is Int) {
size = value
}
}
for (method in sig.methods) {
if (!method.hasCode || method.name != "<clinit>") {
continue
}
for (match in SHA1_MATCHER.match(method)) {
val len = match[0].intConstant
if (len != SHA1_BYTES) {
continue
}
sha1 = ByteArray(SHA1_BYTES)
for (i in 2 until match.size step 4) {
val k = match[i + 1].intConstant!!
val v = match[i + 2].intConstant!!
sha1[k] = v.toByte()
}
}
}
require(size != null && sha1 != null)
return listOf(
ArtifactLink(
ArtifactType.CLIENT,
ArtifactFormat.JAR,
OperatingSystem.INDEPENDENT,
Architecture.INDEPENDENT,
Jvm.INDEPENDENT,
crc32 = null,
sha1,
size
)
)
}
val loader = library["loader"]
if (loader != null) {
val links = mutableListOf<ArtifactLink>()
val paths = mutableSetOf<String>()
for (method in loader.methods) {
if (method.name != "run" || method.desc != "()V") {
continue
}
for (insn in method.instructions) {
if (insn !is MethodInsnNode || insn.owner != loader.name || !insn.desc.endsWith(")[B")) {
continue
}
// TODO(gpe): extract file size too (tricky due to dummy arguments)
val exprs = getArgumentExpressions(insn) ?: continue
for (expr in exprs) {
val single = expr.singleOrNull() ?: continue
if (single !is LdcInsnNode) {
continue
}
val cst = single.cst
if (cst is String && FILE_NAME_REGEX.matches(cst)) {
paths += cst
}
}
}
}
val hashes = mutableMapOf<AbstractInsnNode, ByteArray>()
for (method in loader.methods) {
for (match in SHA1_CMP_MATCHER.match(method)) {
val sha1 = ByteArray(SHA1_BYTES)
var i = 0
while (i < match.size) {
var n = match[i++].intConstant
if (n != null) {
i++ // ALOAD
}
val index = match[i++].intConstant!!
i++ // BALOAD
var xor = false
if (i + 1 < match.size && match[i + 1].opcode == Opcodes.IXOR) {
i += 2 // ICONST_M1, IXOR
xor = true
}
if (match[i].opcode == Opcodes.IFNE) {
n = 0
i++
} else {
if (n == null) {
n = match[i++].intConstant!!
}
i++ // ICMP_IFNE
}
if (xor) {
n = n.inv()
}
sha1[index] = n.toByte()
}
hashes[match[0]] = sha1
}
}
for (method in loader.methods) {
for (match in PATH_CMP_MATCHER.match(method)) {
val first = match[0]
val ldc = if (first is LdcInsnNode) {
first
} else {
match[1] as LdcInsnNode
}
val path = ldc.cst
if (path !is String) {
continue
}
val acmp = match[2] as JumpInsnNode
val target = if (acmp.opcode == Opcodes.IF_ACMPNE) {
acmp.nextReal
} else {
acmp.label.nextReal
}
val hash = hashes.remove(target) ?: continue
if (!paths.remove(path)) {
logger.warn { "Adding link for unused file $path" }
}
links += parseLink(path, hash)
}
}
if (paths.size != hashes.size || paths.size > 1) {
throw IllegalArgumentException()
} else if (paths.size == 1) {
links += parseLink(paths.single(), hashes.values.single())
}
return links
}
// TODO(gpe)
return emptyList()
}
private fun parseLink(path: String, sha1: ByteArray): ArtifactLink {
val m = FILE_NAME_REGEX.matchEntire(path) ?: throw IllegalArgumentException()
val (name, crc1, ext, crc2) = m.destructured
val type = when (name) {
// TODO(gpe): funorb loaders
"runescape", "client" -> ArtifactType.CLIENT
"unpackclass" -> ArtifactType.UNPACKCLASS
"jogl", "jogltrimmed" -> ArtifactType.JOGL
"jogl_awt" -> ArtifactType.JOGL_AWT
else -> throw IllegalArgumentException()
}
val format = when (ext) {
"pack200" -> ArtifactFormat.PACK200
"js5" -> ArtifactFormat.PACKCLASS
"jar", "pack" -> ArtifactFormat.JAR
"dll" -> ArtifactFormat.NATIVE
else -> throw IllegalArgumentException()
}
val os = if (format == ArtifactFormat.NATIVE) OperatingSystem.WINDOWS else OperatingSystem.INDEPENDENT
val arch = if (format == ArtifactFormat.NATIVE) Architecture.X86 else Architecture.INDEPENDENT
val jvm = if (format == ArtifactFormat.NATIVE) Jvm.SUN else Jvm.INDEPENDENT
val crc = crc1.toIntOrNull() ?: crc2.toIntOrNull() ?: throw IllegalArgumentException()
return ArtifactLink(
type,
format,
os,
arch,
jvm,
crc,
sha1,
null
)
}
private fun ByteBuf.hasPrefix(bytes: ByteArray): Boolean {
Unpooled.wrappedBuffer(bytes).use { prefix ->
val len = prefix.readableBytes()
if (readableBytes() < len) {
return false
}
return slice(readerIndex(), len) == prefix
}
}
private companion object {
private val logger = InlineLogger()
private val CAB = byteArrayOf('M'.code.toByte(), 'S'.code.toByte(), 'C'.code.toByte(), 'F'.code.toByte())
private val ELF = byteArrayOf(0x7F, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())
private val JAR = byteArrayOf('P'.code.toByte(), 'K'.code.toByte(), 0x03, 0x04)
private val MACHO32BE = byteArrayOf(0xFE.toByte(), 0xED.toByte(), 0xFA.toByte(), 0xCE.toByte())
private val MACHO32LE = byteArrayOf(0xCE.toByte(), 0xFA.toByte(), 0xED.toByte(), 0xFE.toByte())
private val MACHO64BE = byteArrayOf(0xFE.toByte(), 0xED.toByte(), 0xFA.toByte(), 0xCF.toByte())
private val MACHO64LE = byteArrayOf(0xCF.toByte(), 0xFA.toByte(), 0xED.toByte(), 0xFE.toByte())
private val MACHO_UNIVERSAL = byteArrayOf(0xCA.toByte(), 0xFE.toByte(), 0xBA.toByte(), 0xBE.toByte())
private val PACK200 = byteArrayOf(0x08)
private val PACKCLASS_UNCOMPRESSED = byteArrayOf(0x00)
private val PACKCLASS_BZIP2 = byteArrayOf(0x01)
private val PACKCLASS_GZIP = byteArrayOf(0x02)
private val PE = byteArrayOf('M'.code.toByte(), 'Z'.code.toByte())
private const val OLD_ENGINE_VERSION_STRING = "RS2 user client - release #"
private val OLD_ENGINE_VERSION_MATCHER =
InsnMatcher.compile("LDC INVOKESPECIAL (ICONST | BIPUSH | SIPUSH | LDC)")
private val NEW_ENGINE_RESOLUTIONS = listOf(765, 503, 1024, 768)
private val NEW_ENGINE_BUILDS = 402..916
private const val COMBINED_BUILD = 555
private val COMBINED_TIMESTAMP = LocalDate.of(2009, Month.SEPTEMBER, 2)
.atStartOfDay(ZoneOffset.UTC)
.toInstant()
private const val ARCH_SPARCV9 = 43
private const val SOLARIS_COMMENT = "Solaris Link Editors:"
private const val SHA1_BYTES = 20
private val SHA1_MATCHER =
InsnMatcher.compile("BIPUSH NEWARRAY (DUP (ICONST | BIPUSH) (ICONST | BIPUSH | SIPUSH) IASTORE)+")
private val FILE_NAME_REGEX = Regex("([a-z_]+)(?:_(-?[0-9]+))?[.]([a-z0-9]+)(?:\\?crc=(-?[0-9]+))?")
private val SHA1_CMP_MATCHER =
InsnMatcher.compile("((ICONST | BIPUSH)? ALOAD (ICONST | BIPUSH) BALOAD (ICONST IXOR)? (ICONST | BIPUSH)? (IF_ICMPEQ | IF_ICMPNE | IFEQ | IFNE))+")
private val PATH_CMP_MATCHER = InsnMatcher.compile("(LDC ALOAD | ALOAD LDC) (IF_ACMPEQ | IF_ACMPNE)")
}
}

@ -0,0 +1,30 @@
package org.openrs2.archive.client
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.defaultStdout
import com.github.ajalt.clikt.parameters.types.long
import com.github.ajalt.clikt.parameters.types.outputStream
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
import java.io.FileNotFoundException
public class ExportCommand : CliktCommand(name = "export") {
private val id by argument().long()
private val output by argument().outputStream().defaultStdout()
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val exporter = injector.getInstance(ClientExporter::class.java)
val artifact = exporter.export(id) ?: throw FileNotFoundException()
try {
val buf = artifact.content()
buf.readBytes(output, buf.readableBytes())
} finally {
artifact.release()
}
}
}
}

@ -0,0 +1,32 @@
package org.openrs2.archive.client
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.path
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class ImportCommand : CliktCommand(name = "import") {
private val name by option()
private val description by option()
private val url by option()
private val skipErrors by option().flag()
private val input by argument().path(
mustExist = true,
canBeDir = false,
mustBeReadable = true,
).multiple()
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(ClientImporter::class.java)
importer.import(input, name, description, url, skipErrors)
}
}
}

@ -0,0 +1,7 @@
package org.openrs2.archive.client
public enum class Jvm {
INDEPENDENT,
SUN,
MICROSOFT
}

@ -0,0 +1,116 @@
package org.openrs2.archive.client
import io.netty.buffer.ByteBuf
import org.openrs2.buffer.readString
public data class MachO(
public val architecture: Architecture,
public val symbols: Set<String>,
) {
public companion object {
private const val MACHO_UNIVERSAL = 0xCAFEBABE.toInt()
private const val MACHO32BE = 0xFEEDFACE.toInt()
private const val MACHO32LE = 0xCEFAEDFE.toInt()
private const val MACHO64BE = 0xFEEDFACF.toInt()
private const val MACHO64LE = 0xCFFAEDFE.toInt()
private const val CPU_TYPE_X86 = 0x7
private const val CPU_TYPE_AMD64 = 0x1000007
private const val CPU_TYPE_POWERPC = 0x12
private const val COMMAND_SYMTAB = 0x2
public fun parse(buf: ByteBuf): MachO {
val magic = buf.getInt(buf.readerIndex())
return if (magic == MACHO_UNIVERSAL) {
parseFat(buf)
} else {
parseMachO(buf)
}
}
private fun parseFat(buf: ByteBuf): MachO {
buf.skipBytes(4)
val symbols = mutableSetOf<String>()
val count = buf.readInt()
for (i in 0 until count) {
buf.skipBytes(8)
val offset = buf.readInt()
val size = buf.readInt()
buf.skipBytes(4)
symbols += parseMachO(buf.slice(offset, size)).symbols
}
return MachO(Architecture.UNIVERSAL, symbols)
}
private fun parseMachO(buf: ByteBuf): MachO {
val magic = buf.readInt()
require(magic == MACHO32BE || magic == MACHO32LE || magic == MACHO64BE || magic == MACHO64LE)
val big = magic == MACHO32BE || magic == MACHO64BE
val x64 = magic == MACHO64LE || magic == MACHO64BE
val arch = when (if (big) buf.readInt() else buf.readIntLE()) {
CPU_TYPE_X86 -> Architecture.X86
CPU_TYPE_AMD64 -> Architecture.AMD64
CPU_TYPE_POWERPC -> Architecture.POWERPC
else -> throw IllegalArgumentException()
}
buf.skipBytes(4) // cpuSubType
buf.skipBytes(4) // fileType
val nCmds = if (big) buf.readInt() else buf.readIntLE()
buf.skipBytes(4) // sizeOfCmds
buf.skipBytes(4) // flags
if (x64) {
buf.skipBytes(4) // reserved
}
val symbols = parseCommands(buf, big, nCmds)
return MachO(arch, symbols)
}
private fun parseCommands(buf: ByteBuf, big: Boolean, count: Int): Set<String> {
for (i in 0 until count) {
val base = buf.readerIndex()
val command = if (big) buf.readInt() else buf.readIntLE()
val size = if (big) buf.readInt() else buf.readIntLE()
if (command == COMMAND_SYMTAB) {
buf.skipBytes(8)
val strOff = if (big) buf.readInt() else buf.readIntLE()
val strSize = if (big) buf.readInt() else buf.readIntLE()
return parseStringTable(buf.slice(strOff, strSize))
}
buf.readerIndex(base + size)
}
return emptySet()
}
private fun parseStringTable(buf: ByteBuf): Set<String> {
return buildSet {
while (buf.isReadable) {
val str = buf.readString(Charsets.US_ASCII)
if (str.isNotEmpty()) {
add(str)
}
}
}
}
}
}

@ -0,0 +1,43 @@
package org.openrs2.archive.client
import io.ktor.http.ContentType
public enum class OperatingSystem {
INDEPENDENT,
WINDOWS,
MACOS,
LINUX,
SOLARIS;
public fun getPrefix(): String {
return when (this) {
INDEPENDENT -> throw IllegalArgumentException()
WINDOWS -> ""
else -> "lib"
}
}
public fun getExtension(): String {
return when (this) {
INDEPENDENT -> throw IllegalArgumentException()
WINDOWS -> "dll"
MACOS -> "dylib"
LINUX, SOLARIS -> "so"
}
}
public fun getContentType(): ContentType {
return when (this) {
INDEPENDENT -> throw IllegalArgumentException()
WINDOWS -> PE
MACOS -> MACHO
LINUX, SOLARIS -> ELF_SHARED
}
}
private companion object {
private val ELF_SHARED = ContentType("application", "x-sharedlib")
private val MACHO = ContentType("application", "x-mach-binary")
private val PE = ContentType("application", "vnd.microsoft.portable-executable")
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.client
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class RefreshCommand : CliktCommand(name = "refresh") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(ClientImporter::class.java)
importer.refresh()
}
}
}

@ -0,0 +1,11 @@
package org.openrs2.archive.game
public data class Game(
public val id: Int,
public val url: String?,
public val buildMajor: Int?,
public val buildMinor: Int?,
public val lastMasterIndexId: Int?,
public val languageId: Int,
public val scopeId: Int
)

@ -0,0 +1,58 @@
package org.openrs2.archive.game
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.openrs2.db.Database
@Singleton
public class GameDatabase @Inject constructor(
private val database: Database
) {
public suspend fun getGame(name: String, environment: String, language: String): Game? {
return database.execute { connection ->
connection.prepareStatement(
"""
SELECT v.id, v.url, v.build_major, v.build_minor, v.last_master_index_id, v.language_id, g.scope_id
FROM game_variants v
JOIN games g ON g.id = v.game_id
JOIN environments e ON e.id = v.environment_id
JOIN languages l ON l.id = v.language_id
WHERE g.name = ? AND e.name = ? AND l.iso_code = ?
""".trimIndent()
).use { stmt ->
stmt.setString(1, name)
stmt.setString(2, environment)
stmt.setString(3, language)
stmt.executeQuery().use { rows ->
if (!rows.next()) {
return@execute null
}
val id = rows.getInt(1)
val url: String? = rows.getString(2)
var buildMajor: Int? = rows.getInt(3)
if (rows.wasNull()) {
buildMajor = null
}
var buildMinor: Int? = rows.getInt(4)
if (rows.wasNull()) {
buildMinor = null
}
var lastMasterIndexId: Int? = rows.getInt(5)
if (rows.wasNull()) {
lastMasterIndexId = null
}
val languageId = rows.getInt(6)
val scopeId = rows.getInt(7)
return@execute Game(id, url, buildMajor, buildMinor, lastMasterIndexId, languageId, scopeId)
}
}
}
}
}

@ -0,0 +1,70 @@
package org.openrs2.archive.jav
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.withContext
import org.openrs2.http.checkStatusCode
import java.io.BufferedReader
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
public data class JavConfig(
public val config: Map<String, String>,
public val params: Map<String, String>,
public val messages: Map<String, String>
) {
public companion object {
public suspend fun download(client: HttpClient, url: String): JavConfig {
val request = HttpRequest.newBuilder(URI(url))
.GET()
.timeout(Duration.ofSeconds(30))
.build()
val response = client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).await()
response.checkStatusCode()
return withContext(Dispatchers.IO) {
response.body().bufferedReader().use { reader ->
read(reader)
}
}
}
public fun read(reader: BufferedReader): JavConfig {
val config = mutableMapOf<String, String>()
val params = mutableMapOf<String, String>()
val messages = mutableMapOf<String, String>()
reader.lineSequence().map(String::trim).forEach { line ->
when {
line.startsWith("//") || line.startsWith("#") -> Unit
line.startsWith("msg=") -> {
val parts = line.substring("msg=".length).split("=", limit = 2)
if (parts.size == 2) {
messages[parts[0]] = parts[1]
}
}
line.startsWith("param=") -> {
val parts = line.substring("param=".length).split("=", limit = 2)
if (parts.size == 2) {
params[parts[0]] = parts[1]
}
}
else -> {
val parts = line.split("=", limit = 2)
if (parts.size == 2) {
config[parts[0]] = parts[1]
}
}
}
}
return JavConfig(config, params, messages)
}
}
}

@ -0,0 +1,65 @@
package org.openrs2.archive.key
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import org.openrs2.buffer.use
import org.openrs2.crypto.SymmetricKey
import java.io.InputStream
public object BinaryKeyReader : KeyReader {
override fun read(input: InputStream): Sequence<SymmetricKey> {
Unpooled.wrappedBuffer(input.readBytes()).use { buf ->
val len = buf.readableBytes()
if (len == (128 * 128 * 16)) {
val keys = read(buf, 0)
require(SymmetricKey.ZERO in keys)
return keys.asSequence()
}
val maybeShort = (len % 18) == 0
val maybeInt = (len % 20) == 0
if (maybeShort && !maybeInt) {
val keys = read(buf, 2)
require(SymmetricKey.ZERO in keys)
return keys.asSequence()
} else if (!maybeShort && maybeInt) {
val keys = read(buf, 4).asSequence()
require(SymmetricKey.ZERO in keys)
return keys.asSequence()
} else if (maybeShort && maybeInt) {
val shortKeys = read(buf, 2)
val intKeys = read(buf, 4)
return if (SymmetricKey.ZERO in shortKeys && SymmetricKey.ZERO !in intKeys) {
shortKeys.asSequence()
} else if (SymmetricKey.ZERO !in shortKeys && SymmetricKey.ZERO in intKeys) {
intKeys.asSequence()
} else {
throw IllegalArgumentException("Failed to determine if map square IDs are 2 or 4 bytes")
}
} else {
throw IllegalArgumentException(
"Binary XTEA files must be exactly 256 KiB or a multiple of 18 or 20 bytes long"
)
}
}
}
private fun read(buf: ByteBuf, mapSquareLen: Int): Set<SymmetricKey> {
val keys = mutableSetOf<SymmetricKey>()
while (buf.isReadable) {
buf.skipBytes(mapSquareLen)
val k0 = buf.readInt()
val k1 = buf.readInt()
val k2 = buf.readInt()
val k3 = buf.readInt()
keys += SymmetricKey(k0, k1, k2, k3)
}
return keys
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.key
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class BruteForceCommand : CliktCommand(name = "brute-force") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val bruteForcer = injector.getInstance(KeyBruteForcer::class.java)
bruteForcer.bruteForce()
}
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.key
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class DownloadCommand : CliktCommand(name = "download") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(KeyImporter::class.java)
importer.download()
}
}
}

@ -0,0 +1,16 @@
package org.openrs2.archive.key
import com.github.ajalt.clikt.core.CliktCommand
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class EntCommand : CliktCommand(name = "ent") {
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val exporter = injector.getInstance(KeyExporter::class.java)
println(exporter.analyse())
}
}
}

@ -0,0 +1,57 @@
package org.openrs2.archive.key
import jakarta.inject.Inject
import jakarta.inject.Singleton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.withContext
import org.openrs2.crypto.SymmetricKey
import org.openrs2.http.checkStatusCode
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
@Singleton
public class HdosKeyDownloader @Inject constructor(
private val client: HttpClient
) : KeyDownloader(KeySource.HDOS) {
override suspend fun getMissingUrls(seenUrls: Set<String>): Set<String> {
return setOf(ENDPOINT)
}
override suspend fun download(url: String): Sequence<SymmetricKey> {
val request = HttpRequest.newBuilder(URI(url))
.GET()
.timeout(Duration.ofSeconds(30))
.build()
val response = client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).await()
response.checkStatusCode()
return withContext(Dispatchers.IO) {
response.body().use { input ->
input.bufferedReader().use { reader ->
val keys = mutableSetOf<SymmetricKey>()
for (line in reader.lineSequence()) {
val parts = line.split(',')
if (parts.size < 3) {
continue
}
val key = SymmetricKey.fromHexOrNull(parts[2]) ?: continue
keys += key
}
keys.asSequence()
}
}
}
}
private companion object {
private const val ENDPOINT = "https://api.hdos.dev/keys/get"
}
}

@ -0,0 +1,13 @@
package org.openrs2.archive.key
import org.openrs2.crypto.SymmetricKey
import java.io.InputStream
public object HexKeyReader : KeyReader {
override fun read(input: InputStream): Sequence<SymmetricKey> {
return input.bufferedReader()
.lineSequence()
.map(SymmetricKey::fromHexOrNull)
.filterNotNull()
}
}

@ -0,0 +1,23 @@
package org.openrs2.archive.key
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import com.google.inject.Guice
import kotlinx.coroutines.runBlocking
import org.openrs2.archive.ArchiveModule
import org.openrs2.inject.CloseableInjector
public class ImportCommand : CliktCommand(name = "import") {
private val input by argument().path(
mustExist = true,
mustBeReadable = true
)
override fun run(): Unit = runBlocking {
CloseableInjector(Guice.createInjector(ArchiveModule)).use { injector ->
val importer = injector.getInstance(KeyImporter::class.java)
importer.import(input)
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save