diff --git a/build.gradle.kts b/build.gradle.kts index d9da869564..d610b5df85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,7 +36,7 @@ buildscript { dependencies { classpath("org.ajoberstar.grgit:grgit-core:4.1.0") classpath("com.github.ben-manes:gradle-versions-plugin:0.36.0") - classpath("com.openosrs:injector-plugin:1.2.0") + classpath("com.openosrs:openosrs-injector:1.3.0") } } diff --git a/gradle.properties b/gradle.properties index dbf57649ed..d34c6d562c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.caching=true +org.gradle.caching=false org.gradle.warning.mode=all org.gradle.parallel=true org.gradle.console=rich diff --git a/openosrs-injector/.gitignore b/openosrs-injector/.gitignore new file mode 100644 index 0000000000..883f2555e2 --- /dev/null +++ b/openosrs-injector/.gitignore @@ -0,0 +1,3 @@ +/build/ +/.gradle/ +/.idea/ diff --git a/openosrs-injector/LICENSE b/openosrs-injector/LICENSE new file mode 100644 index 0000000000..f288702d2f --- /dev/null +++ b/openosrs-injector/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/openosrs-injector/gradle/wrapper/gradle-wrapper.jar b/openosrs-injector/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..62d4c05355 Binary files /dev/null and b/openosrs-injector/gradle/wrapper/gradle-wrapper.jar differ diff --git a/openosrs-injector/gradle/wrapper/gradle-wrapper.properties b/openosrs-injector/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..622ab64a3c --- /dev/null +++ b/openosrs-injector/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/openosrs-injector/gradlew b/openosrs-injector/gradlew new file mode 100644 index 0000000000..fbd7c51583 --- /dev/null +++ b/openosrs-injector/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/openosrs-injector/gradlew.bat b/openosrs-injector/gradlew.bat new file mode 100644 index 0000000000..5093609d51 --- /dev/null +++ b/openosrs-injector/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/openosrs-injector/jitpack.yml b/openosrs-injector/jitpack.yml new file mode 100644 index 0000000000..848b36414a --- /dev/null +++ b/openosrs-injector/jitpack.yml @@ -0,0 +1,6 @@ +# JitPack publishing yml - used for CI and externals +jdk: + - openjdk11 +install: + - chmod a+x ./gradlew + - ./gradlew clean build publishToMavenLocal -x test --console=plain diff --git a/openosrs-injector/openosrs-injector.gradle.kts b/openosrs-injector/openosrs-injector.gradle.kts new file mode 100644 index 0000000000..c9c7a0e938 --- /dev/null +++ b/openosrs-injector/openosrs-injector.gradle.kts @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("java-gradle-plugin") + kotlin("jvm") version "1.3.72" + `maven-publish` + id("se.patrikerdes.use-latest-versions") +} + +val oprsver = "3.5.1" + +group = "com.openosrs" +version = "1.3.0" + +repositories { + mavenCentral() + mavenLocal() + maven { + url = uri("https://repo.runelite.net") + url = uri("https://raw.githubusercontent.com/open-osrs/hosting/master") + url = uri("https://repo.openosrs.com/repository/maven") + } +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:1.18.12") + compileOnly("org.projectlombok:lombok:1.18.12") + + implementation("org.ow2.asm:asm:8.0.1") + implementation("org.ow2.asm:asm-util:8.0.1") + implementation("org.jetbrains:annotations:19.0.0") + implementation("com.google.guava:guava:29.0-jre") + implementation("com.openosrs:deobfuscator:${oprsver}") { + isTransitive = false + } + + testCompileOnly("com.openosrs:injection-annotations:1.0") + testImplementation("junit:junit:4.13") +} + +gradlePlugin { + plugins { + create("injectorPlugin") { + id = "com.openosrs.injector" + implementationClass = "com.openosrs.injector.InjectPlugin" + } + } +} + +configure { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} + +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} + +val sourcesJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) +} + +publishing { + repositories { + mavenLocal() + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/InjectException.java b/openosrs-injector/src/main/java/com/openosrs/injector/InjectException.java new file mode 100644 index 0000000000..aa1a352a0d --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/InjectException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector; + +public class InjectException extends RuntimeException +{ + public InjectException(String message) + { + super(message); + } + + public InjectException(Throwable cause) + { + super(cause); + } + + public InjectException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/InjectUtil.java b/openosrs-injector/src/main/java/com/openosrs/injector/InjectUtil.java new file mode 100644 index 0000000000..b924d035e6 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/InjectUtil.java @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector; + +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.rsapi.RSApiClass; +import com.openosrs.injector.rsapi.RSApiMethod; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import net.runelite.asm.Annotation; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Named; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotated; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.DLoad; +import net.runelite.asm.attributes.code.instructions.FLoad; +import net.runelite.asm.attributes.code.instructions.ILoad; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LLoad; +import net.runelite.asm.attributes.code.instructions.LMul; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.attributes.code.instructions.VReturn; +import net.runelite.asm.pool.Class; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import net.runelite.deob.deobfuscators.arithmetic.DMath; +import org.jetbrains.annotations.Nullable; +import static com.openosrs.injector.rsapi.RSApi.API_BASE; +import static com.openosrs.injector.rsapi.RSApi.RL_API_BASE; + +public interface InjectUtil +{ + /** + * Finds a static method in deob and converts it to ob + * + * @param data InjectData instance + * @param name The name of the method you want to find + * @return The obfuscated version of the found method + */ + static Method findMethod(InjectData data, String name) + { + return findMethod(data, name, null, null); + } + + /** + * Finds a static method in deob and converts it to ob + * + * @param data InjectData instance + * @param name The name of the method you want to find + * @param classHint The name of the class you expect the method to be in, or null + * @param sig The signature the method has in deob, or null + * + * @return The obfuscated version of the found method + */ + static Method findMethod( + InjectData data, + String name, + String classHint, + @Nullable Predicate sig) + { + return findMethod(data, name, classHint, sig, false, false); + } + + /** + * Finds a method in injectData's deobfuscated classes. + * + * @param data InjectData instance + * @param name (Exported) method name + * @param classHint The (exported) name of a class you expect (or know) the method to be in, or null, if you're not sure + * @throws InjectException If the hint class couldn't be found, or no method matching the settings was found + */ + static Method findMethod( + InjectData data, + String name, + @Nullable String classHint) + throws InjectException + { + return findMethod(data, name, classHint, null, false, false); + } + + /** + * Finds a method in injectData's deobfuscated classes. + * + * @param data InjectData instance + * @param name (Exported) method name + * @param classHint The (exported) name of a class you expect (or know) the method to be in, or null, if you're not sure + * @param sig The deobfuscated methods' signature, or null, if you're unsure + * @param notStatic If this is true, only check non-static methods. If classHint isn't null, check only subclasses + * @param returnDeob If this is true, this method will return the deobfuscated method, instead of turning it into vanilla first + * + * @throws InjectException If the hint class couldn't be found, or no method matching the settings was found + */ + static Method findMethod( + InjectData data, + String name, + @Nullable String classHint, + @Nullable Predicate sig, + boolean notStatic, + boolean returnDeob) + throws InjectException + { + final ClassGroup deob = data.getDeobfuscated(); + if (classHint != null) + { + ClassFile cf; + Method m; + cf = findClassOrThrow(deob, classHint); + if (notStatic) + { + if (sig == null) + m = InjectUtil.findMethodDeep(cf, name, s -> true); + else + m = InjectUtil.findMethodDeep(cf, name, sig); + } + else + m = cf.findMethod(name); + + if (m != null) + return returnDeob ? m : data.toVanilla(m); + } + + for (ClassFile cf : deob) + for (Method m : cf.getMethods()) + if (m.getName().equals(name)) + if (!notStatic || !m.isStatic()) + if (sig == null || sig.test(m.getDescriptor())) + return returnDeob ? m : data.toVanilla(m); + + throw new InjectException(String.format("Couldn't find %s", name)); + } + + static ClassFile findClassOrThrow(ClassGroup group, String name) + { + ClassFile clazz = group.findClass(name); + if (clazz == null) + throw new InjectException("Hint class " + name + " doesn't exist"); + + return clazz; + } + + /** + * Fail-fast implementation of ClassFile.findMethodDeep, using a predicate for signature + */ + static Method findMethodDeep(ClassFile clazz, String name, Predicate type) + { + do + for (Method method : clazz.getMethods()) + if (method.getName().equals(name)) + if (type.test(method.getDescriptor())) + return method; + while ((clazz = clazz.getParent()) != null); + + throw new InjectException(String.format("Method %s couldn't be found", name + type.toString())); + } + + /** + * Fail-fast implementation of ClassGroup.findStaticField + * + * well... + */ + static Field findStaticField(ClassGroup group, String name) + { + for (ClassFile clazz : group) + { + Field f = clazz.findField(name); + if (f != null && f.isStatic()) + return f; + } + + throw new InjectException("Couldn't find static field " + name); + } + + /** + * Finds a static field in deob and converts it to ob + * + * @param data InjectData instance + * @param name The name of the field you want to find + * @param classHint The name of the class you expect the field to be in, or null + * @param type The type the method has in deob, or null + * + * @return The obfuscated version of the found field + */ + static Field findStaticField(InjectData data, String name, String classHint, Type type) + { + final ClassGroup deob = data.getDeobfuscated(); + Field field; + + if (classHint != null) + { + ClassFile clazz = findClassOrThrow(deob, classHint); + + if (type == null) + field = clazz.findField(name); + else + field = clazz.findField(name, type); + + if (field != null) + return field; + } + + for (ClassFile clazz : deob) + { + if (type == null) + field = clazz.findField(name); + else + field = clazz.findField(name, type); + + if (field != null) + return field; + } + + throw new InjectException(String.format("Static field %s doesn't exist", (type != null ? type + " " : "") + name)); + } + + /** + * Fail-fast implementation of ClassGroup.findFieldDeep + */ + static Field findFieldDeep(ClassFile clazz, String name) + { + Field f; + + do + if ((f = clazz.findField(name)) != null) + return f; + while ((clazz = clazz.getParent()) != null); + + throw new InjectException("Couldn't find field " + name); + } + + static Field findField(InjectData data, String name, String hintClass) + { + final ClassGroup deob = data.getDeobfuscated(); + return data.toVanilla(findField(deob, name, hintClass)); + } + + static Field findField(ClassGroup group, String name, String hintClass) + { + Field field; + if (hintClass != null) + { + ClassFile clazz = findClassOrThrow(group, hintClass); + + field = clazz.findField(name); + if (field != null) + return field; + } + + for (ClassFile clazz : group) + if ((field = clazz.findField(name)) != null) + return field; + + throw new InjectException("Field " + name + " doesn't exist"); + } + + static ClassFile deobFromApiMethod(InjectData data, RSApiMethod apiMethod) + { + return data.toDeob(apiMethod.getClazz().getName()); + } + + static ClassFile vanillaFromApiMethod(InjectData data, RSApiMethod apiMethod) + { + return data.toVanilla(deobFromApiMethod(data, apiMethod)); + } + + static Signature apiToDeob(InjectData data, Signature api) + { + return new Signature.Builder() + .setReturnType(apiToDeob(data, api.getReturnValue())) + .addArguments( + api.getArguments().stream() + .map(type -> apiToDeob(data, type)) + .collect(Collectors.toList()) + ).build(); + } + + static Type apiToDeob(InjectData data, Type api) + { + if (api.isPrimitive()) + return api; + + final String internalName = api.getInternalName(); + if (internalName.startsWith(API_BASE)) + return Type.getType("L" + api.getInternalName().substring(API_BASE.length()) + ";", api.getDimensions()); + else if (internalName.startsWith(RL_API_BASE)) + { + Class rlApiC = new Class(internalName); + RSApiClass highestKnown = data.getRsApi().withInterface(rlApiC); + + // Cheeky unchecked exception + assert highestKnown != null : "No rs api class implements rl api class " + rlApiC.toString(); + + boolean changed; + do + { + changed = false; + + for (RSApiClass interf : highestKnown.getApiInterfaces()) + { + if (interf.getInterfaces().contains(rlApiC)) + { + highestKnown = interf; + changed = true; + break; + } + } + } + while (changed); + + return apiToDeob(data, Type.getType(highestKnown.getName(), api.getDimensions())); + } + + return api; + } + + static Type deobToVanilla(InjectData data, Type deobT) + { + if (deobT.isPrimitive()) + return deobT; + + final ClassFile deobClass = data.getDeobfuscated().findClass(deobT.getInternalName()); + if (deobClass == null) + return deobT; + + return Type.getType("L" + data.toVanilla(deobClass).getName() + ";", deobT.getDimensions()); + } + + static boolean apiToDeobSigEquals(InjectData data, Signature deobSig, Signature apiSig) + { + return deobSig.equals(apiToDeob(data, apiSig)); + } + + static boolean argsMatch(Signature a, Signature b) + { + List aa = a.getArguments(); + List bb = b.getArguments(); + + if (aa.size() != bb.size()) + return false; + + for (int i = 0; i < aa.size(); i++) + if (!aa.get(i).equals(bb.get(i))) + return false; + + return true; + } + + /** + * Gets the obfuscated name from something's annotations. + * + * If the annotation doesn't exist return the current name instead. + */ + static String getObfuscatedName(T from) + { + Annotation name = from.findAnnotation(DeobAnnotations.OBFUSCATED_NAME); + return name == null ? from.getName() : name.getValueString(); + } + + /** + * Gets the value of the @Export annotation on the object. + */ + static String getExportedName(Annotated from) + { + Annotation export = from.findAnnotation(DeobAnnotations.EXPORT); + return export == null ? null : export.getValueString(); + } + + /** + * Creates the correct load instruction for the variable with Type type and var index index. + */ + static Instruction createLoadForTypeIndex(Instructions instructions, Type type, int index) + { + if (type.getDimensions() > 0 || !type.isPrimitive()) + return new ALoad(instructions, index); + + switch (type.toString()) + { + case "B": + case "C": + case "I": + case "S": + case "Z": + return new ILoad(instructions, index); + case "D": + return new DLoad(instructions, index); + case "F": + return new FLoad(instructions, index); + case "J": + return new LLoad(instructions, index); + default: + throw new IllegalStateException("Unknown type"); + } + } + + /** + * Creates the right return instruction for an object with Type type + */ + static Instruction createReturnForType(Instructions instructions, Type type) + { + if (!type.isPrimitive()) + return new Return(instructions, InstructionType.ARETURN); + + switch (type.toString()) + { + case "B": + case "C": + case "I": + case "S": + case "Z": + return new Return(instructions, InstructionType.IRETURN); + case "D": + return new Return(instructions, InstructionType.DRETURN); + case "F": + return new Return(instructions, InstructionType.FRETURN); + case "J": + return new Return(instructions, InstructionType.LRETURN); + case "V": + return new VReturn(instructions); + default: + throw new IllegalStateException("Unknown type"); + } + } + + static Instruction createInvokeFor(Instructions instructions, net.runelite.asm.pool.Method method, boolean isStatic) + { + if (isStatic) + return new InvokeStatic(instructions, method); + else + return new InvokeVirtual(instructions, method); + } + + /** + * Legit fuck annotations + */ + static ClassFile getVanillaClassFromAnnotationString(InjectData data, Annotation annotation) + { + Object v = annotation.getValue(); + String str = ((org.objectweb.asm.Type) v).getInternalName(); + return data.toVanilla(data.toDeob(str)); + } + + /** + * Add after the get + */ + static void injectObfuscatedGetter(Number getter, Instructions instrs, Consumer into) + { + into.accept(new LDC(instrs, getter)); + + if (getter instanceof Integer) + into.accept(new IMul(instrs)); + else if (getter instanceof Long) + into.accept(new LMul(instrs)); + } + + /** + * Add IN FRONT of the put + * + * @param getter should be the same value as for the getter (straight from ObfuscatedGetter) + */ + static void injectObfuscatedSetter(Number getter, Instructions instrs, Consumer into) + { + injectObfuscatedGetter(DMath.modInverse(getter), instrs, into); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/Injection.java b/openosrs-injector/src/main/java/com/openosrs/injector/Injection.java new file mode 100644 index 0000000000..76d58daa86 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/Injection.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector; + +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.injection.InjectTaskHandler; +import com.openosrs.injector.injectors.CreateAnnotations; +import com.openosrs.injector.injectors.InjectConstruct; +import com.openosrs.injector.injectors.Injector; +import com.openosrs.injector.injectors.InterfaceInjector; +import com.openosrs.injector.injectors.MixinInjector; +import com.openosrs.injector.injectors.RSApiInjector; +import com.openosrs.injector.injectors.raw.AddPlayerToMenu; +import com.openosrs.injector.injectors.raw.ClearColorBuffer; +import com.openosrs.injector.injectors.raw.DrawAfterWidgets; +import com.openosrs.injector.injectors.raw.DrawMenu; +import com.openosrs.injector.injectors.raw.Occluder; +import com.openosrs.injector.injectors.raw.RasterizerAlpha; +import com.openosrs.injector.injectors.raw.RenderDraw; +import com.openosrs.injector.injectors.raw.ScriptVM; +import com.openosrs.injector.rsapi.RSApi; +import com.openosrs.injector.transformers.InjectTransformer; +import com.openosrs.injector.transformers.SourceChanger; +import java.io.File; +import java.io.IOException; +import net.runelite.deob.util.JarUtil; +import org.gradle.api.file.FileTree; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; + +public class Injection extends InjectData implements InjectTaskHandler +{ + private static final Logger log = Logging.getLogger(Injection.class); + + public Injection(File vanilla, File rsclient, File mixins, FileTree rsapi) throws IOException + { + super( + JarUtil.loadJar(vanilla), + JarUtil.loadJar(rsclient), + JarUtil.loadJar(mixins), + new RSApi(rsapi) + ); + } + + public void inject() + { + log.debug("[DEBUG] Starting injection"); + + inject(new CreateAnnotations(this)); + + inject(new InterfaceInjector(this)); + + inject(new RasterizerAlpha(this)); + + inject(new MixinInjector(this)); + + // This is where field hooks runs + + // This is where method hooks runs + + inject(new InjectConstruct(this)); + + inject(new RSApiInjector(this)); + + //inject(new DrawAfterWidgets(this)); + + inject(new ScriptVM(this)); + + // All GPU raw injectors should probably be combined, especially RenderDraw and Occluder + inject(new ClearColorBuffer(this)); + + inject(new RenderDraw(this)); + + inject(new Occluder(this)); + + inject(new DrawMenu(this)); + + inject(new AddPlayerToMenu(this)); + + validate(new InjectorValidator(this)); + + transform(new SourceChanger(this)); + } + + public void save(File outputJar) throws IOException + { + log.info("[INFO] Saving jar to {}", outputJar.toString()); + + JarUtil.saveJar(this.getVanilla(), outputJar); + } + + private void inject(Injector injector) + { + final String name = injector.getName(); + + log.info("[INFO] Starting {}", name); + + injector.start(); + + injector.inject(); + + log.lifecycle("{} {}", name, injector.getCompletionMsg()); + + if (injector instanceof Validator) + validate((Validator) injector); + } + + private void validate(Validator validator) + { + final String name = validator.getName(); + + if (!validator.validate()) + { + throw new InjectException(name + " failed validation"); + } + } + + private void transform(InjectTransformer transformer) + { + final String name = transformer.getName(); + + log.info("[INFO] Starting {}", name); + + transformer.transform(); + + log.lifecycle("{} {}", name, transformer.getCompletionMsg()); + } + + public void runChildInjector(Injector injector) + { + inject(injector); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/InjectorValidator.java b/openosrs-injector/src/main/java/com/openosrs/injector/InjectorValidator.java new file mode 100644 index 0000000000..4a606a4a22 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/InjectorValidator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector; + +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.rsapi.RSApi; +import com.openosrs.injector.rsapi.RSApiClass; +import com.openosrs.injector.rsapi.RSApiMethod; +import lombok.RequiredArgsConstructor; +import net.runelite.asm.ClassFile; +import net.runelite.asm.pool.Class; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import static com.openosrs.injector.rsapi.RSApi.API_BASE; + +@RequiredArgsConstructor +public class InjectorValidator implements Validator +{ + private static final Logger log = Logging.getLogger(InjectorValidator.class); + private static final String OK = "OK", ERROR = "ERROR", WTF = "WTF"; + private final InjectData inject; + + private int missing = 0, okay = 0, wtf = 0; + + public boolean validate() + { + final RSApi rsApi = inject.getRsApi(); + for (ClassFile cf : inject.getVanilla()) + { + for (Class intf : cf.getInterfaces()) + { + if (!intf.getName().startsWith(API_BASE)) + continue; + + RSApiClass apiC = rsApi.findClass(intf.getName()); + if (apiC == null) + { + log.error("{} is rs api type implemented by {} but it doesn't exist in rsapi. wtf", intf, cf.getPoolClass()); + ++wtf; + continue; + } + + check(cf, apiC); + } + } + + String status = wtf > 0 ? WTF : missing > 0 ? ERROR : OK; + log.info("[INFO] RSApiValidator completed. Status [{}] {} overridden methods, {} missing", status, okay, missing); + + // valid, ref to static final field + return status == OK; + } + + private void check(ClassFile clazz, RSApiClass apiClass) + { + for (RSApiMethod apiMethod : apiClass) + { + if (apiMethod.isSynthetic() || apiMethod.isDefault()) + continue; + + if (clazz.findMethodDeep(apiMethod.getName(), apiMethod.getSignature()) == null) + { + log.warn("[WARN] Class {} implements interface {} but doesn't implement {}", + clazz.getPoolClass(), apiClass.getClazz(), apiMethod.getMethod()); + ++missing; + } + else + { + ++okay; + } + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/Validator.java b/openosrs-injector/src/main/java/com/openosrs/injector/Validator.java new file mode 100644 index 0000000000..ba3b7ef037 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/Validator.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector; + +import net.runelite.asm.Named; + +public interface Validator extends Named +{ + boolean validate(); + + default String getName() + { + return this.getClass().getSimpleName(); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectData.java b/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectData.java new file mode 100644 index 0000000000..3f47bf12ef --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectData.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injection; + +import com.google.common.collect.ImmutableMap; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injectors.Injector; +import com.openosrs.injector.rsapi.RSApi; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import lombok.Getter; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.signature.Signature; + +/** + * Abstract class meant as the interface of {@link com.openosrs.injector.Injection injection} for injectors + */ +public abstract class InjectData +{ + public static final String HOOKS = "net/runelite/client/callback/Hooks"; + + @Getter + private final ClassGroup vanilla; + + @Getter + private final ClassGroup deobfuscated; + + @Getter + private final ClassGroup mixins; + + @Getter + private final RSApi rsApi; + + /** + * Deobfuscated ClassFiles -> Vanilla ClassFiles + */ + private final Map toVanilla; + + /** + * Strings -> Deobfuscated ClassFiles + * keys: + * - Obfuscated name + * - RSApi implementing name + */ + private final Map toDeob = new HashMap<>(); + + public InjectData(ClassGroup vanilla, ClassGroup deobfuscated, ClassGroup mixins, RSApi rsApi) + { + this.vanilla = vanilla; + this.deobfuscated = deobfuscated; + this.rsApi = rsApi; + this.mixins = mixins; + this.toVanilla = initToVanilla(); + } + + public abstract void runChildInjector(Injector injector); + + private Map initToVanilla() + { + ImmutableMap.Builder toVanillaB = ImmutableMap.builder(); + + for (final ClassFile deobClass : deobfuscated) + { + if (deobClass.getName().startsWith("net/runelite/")) + { + continue; + } + + final String obName = InjectUtil.getObfuscatedName(deobClass); + if (obName != null) + { + toDeob.put(obName, deobClass); + + // Can't be null + final ClassFile obClass = this.vanilla.findClass(obName); + toVanillaB.put(deobClass, obClass); + } + } + + return toVanillaB.build(); + } + + /** + * Deobfuscated ClassFile -> Vanilla ClassFile + */ + public ClassFile toVanilla(ClassFile deobClass) + { + return toVanilla.get(deobClass); + } + + /** + * Deobfuscated Method -> Vanilla Method + */ + public Method toVanilla(Method deobMeth) + { + final ClassFile obC = toVanilla(deobMeth.getClassFile()); + + String name = InjectUtil.getObfuscatedName(deobMeth); + + Signature sig = deobMeth.getObfuscatedSignature(); + if (sig == null) + { + sig = deobMeth.getDescriptor(); + } + + return obC.findMethod(name, sig); + } + + /** + * Deobfuscated Field -> Vanilla Field + */ + public Field toVanilla(Field deobField) + { + final ClassFile obC = toVanilla(deobField.getClassFile()); + + String name = InjectUtil.getObfuscatedName(deobField); + + Type type = deobField.getObfuscatedType(); + + return obC.findField(name, type); + } + + /** + * Vanilla ClassFile -> Deobfuscated ClassFile + */ + public ClassFile toDeob(String str) + { + return this.toDeob.get(str); + } + + /** + * Adds a string mapping for a deobfuscated class + */ + public void addToDeob(String key, ClassFile value) + { + toDeob.put(key, value); + } + + /** + * Do something with all paired classes. + * + * Key = deobfuscated, Value = vanilla + */ + public void forEachPair(BiConsumer action) + { + for (Map.Entry pair : toVanilla.entrySet()) + { + action.accept(pair.getKey(), pair.getValue()); + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java b/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java new file mode 100644 index 0000000000..4f7fad4586 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injection; + +import java.io.File; +import java.io.IOException; + +/** + * Interface containing all the methods gradle needs to know about + */ +public interface InjectTaskHandler +{ + /** + * The actual method that does all the work + */ + void inject(); + + /** + * Call this to save the injected jar to outputJar + */ + void save(File outputJar) throws IOException; +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java new file mode 100644 index 0000000000..b1c645c544 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injectors; + +import com.google.common.base.Stopwatch; +import com.openosrs.injector.injection.InjectData; +import lombok.RequiredArgsConstructor; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; + +@RequiredArgsConstructor +public abstract class AbstractInjector implements Injector +{ + protected final InjectData inject; + protected final Logger log = Logging.getLogger(this.getClass()); + private Stopwatch stopwatch; + + public void start() + { + stopwatch = Stopwatch.createStarted(); + } + + public final String getCompletionMsg() + { + return "finished in " + stopwatch.toString(); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java new file mode 100644 index 0000000000..bfabccdd20 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.openosrs.injector.injection.InjectData; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.deob.DeobAnnotations; + +/* + * This handles creating "virtual" annotations to clean up rs-client in the main project + */ +public class CreateAnnotations extends AbstractInjector +{ + + public CreateAnnotations(InjectData inject) + { + super(inject); + } + + public void inject() + { + for (final ClassFile deobClass : inject.getDeobfuscated()) + { + injectFields(deobClass); + injectMethods(deobClass); + + if (deobClass.getName().startsWith("class")) + continue; + + deobClass.addAnnotation(DeobAnnotations.IMPLEMENTS, deobClass.getName()); + } + } + + private void injectFields(ClassFile deobClass) + { + for (Field deobField : deobClass.getFields()) + { + deobField.addAnnotation(DeobAnnotations.EXPORT, deobField.getName()); + } + } + + private void injectMethods(ClassFile deobClass) + { + for (Method deobMethod : deobClass.getMethods()) + { + deobMethod.addAnnotation(DeobAnnotations.EXPORT, deobMethod.getName()); + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java new file mode 100644 index 0000000000..ef1b8e7db0 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.rsapi.RSApiMethod; +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.asm.Annotation; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.Dup; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import net.runelite.asm.attributes.code.instructions.New; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.pool.Class; +import net.runelite.asm.pool.Method; +import net.runelite.asm.signature.Signature; +import static com.openosrs.injector.rsapi.RSApi.CONSTRUCT; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +public class InjectConstruct extends AbstractInjector +{ + private int injected = 0; + + public InjectConstruct(InjectData inject) + { + super(inject); + } + + @Override + public void inject() + { + for (RSApiMethod apiMethod : inject.getRsApi().getConstructs()) + { + Annotation construct = apiMethod.findAnnotation(CONSTRUCT); + if (construct == null) + continue; + + final Method method = apiMethod.getMethod(); + final Class clazz = method.getClazz(); + final ClassFile deobClass = inject.toDeob(clazz.getName()); + final ClassFile vanillaClass = inject.toVanilla(deobClass); + + injectConstruct(vanillaClass, method); + apiMethod.setInjected(true); + injected++; + } + + log.info("[INFO] Injected {} constructors", injected); + } + + private void injectConstruct(ClassFile targetClass, Method apiMethod) + { + log.debug("[DEBUG] Injecting constructor for {} into {}", apiMethod, targetClass.getPoolClass()); + + final Type returnval = apiMethod.getType().getReturnValue(); + + final ClassFile deobClass = inject.toDeob(returnval.getInternalName()); + final ClassFile classToConstruct = inject.toVanilla(deobClass); + + Signature constr = new Signature.Builder() + .addArguments(apiMethod.getType().getArguments().stream() + .map(t -> InjectUtil.apiToDeob(inject, t)) + .map(t -> InjectUtil.deobToVanilla(inject, t)) + .collect(Collectors.toList())) + .setReturnType(Type.VOID) + .build(); + + final net.runelite.asm.Method constructor = classToConstruct.findMethod("", constr); + if (constructor == null) + throw new InjectException("Unable to find constructor for " + classToConstruct.getName() + "." + constr); + + + net.runelite.asm.Method setterMethod = new net.runelite.asm.Method(targetClass, apiMethod.getName(), apiMethod.getType()); + setterMethod.setAccessFlags(ACC_PUBLIC); + targetClass.addMethod(setterMethod); + + final Code code = new Code(setterMethod); + setterMethod.setCode(code); + + final Instructions instructions = code.getInstructions(); + final List ins = instructions.getInstructions(); + + ins.add(new New(instructions, classToConstruct.getPoolClass())); + ins.add(new Dup(instructions)); + + int idx = 1; + int parameter = 0; + + for (Type type : constructor.getDescriptor().getArguments()) + { + Instruction load = InjectUtil.createLoadForTypeIndex(instructions, type, idx); + idx += type.getSize(); + ins.add(load); + + Type paramType = apiMethod.getType().getTypeOfArg(parameter); + if (!type.equals(paramType)) + { + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(type); + ins.add(checkCast); + } + + ++parameter; + } + + ins.add(new InvokeSpecial(instructions, constructor.getPoolMethod())); + ins.add(new Return(instructions)); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java new file mode 100644 index 0000000000..c2d8cde855 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.google.common.collect.Lists; +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Provider; +import lombok.AllArgsConstructor; +import net.runelite.asm.Annotation; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.DupInstruction; +import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction; +import net.runelite.asm.attributes.code.instructions.ArrayStore; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.Dup; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.LMul; +import net.runelite.asm.attributes.code.instructions.PutField; +import net.runelite.asm.attributes.code.instructions.Swap; +import net.runelite.asm.execution.Execution; +import net.runelite.asm.execution.InstructionContext; +import net.runelite.asm.execution.StackContext; +import net.runelite.asm.pool.Class; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; + +public class InjectHook extends AbstractInjector +{ + private static final String CLINIT = ""; + private static final Type FIELDHOOK = new Type("Lnet/runelite/api/mixins/FieldHook;"); + + private final Map hooked = new HashMap<>(); + private final Map, List> mixinTargets; + + private int injectedHooks; + + InjectHook(final InjectData inject, final Map, List> mixinTargets) + { + super(inject); + this.mixinTargets = mixinTargets; + } + + @Override + public void inject() + { + for (Map.Entry, List> entry : mixinTargets.entrySet()) + injectMethods(entry.getKey(), entry.getValue()); + + injectHooks(); + + log.info("[INFO] Injected {} field hooks.", injectedHooks); + } + + private void injectMethods(Provider mixinProvider, List targetClasses) + { + final ClassFile mixinClass = mixinProvider.get(); + + for (ClassFile targetClass : targetClasses) + { + for (Method mixinMethod : mixinClass.getMethods()) + { + final Annotation fieldHook = mixinMethod.findAnnotation(FIELDHOOK); + if (fieldHook == null) + continue; + + final String hookName = fieldHook.getValueString(); + final boolean before = isBefore(fieldHook); + + final ClassFile deobTarget = inject.toDeob(targetClass.getName()); + final Field deobField; + + if (mixinMethod.isStatic()) + { + deobField = InjectUtil.findStaticField(deobTarget.getGroup(), hookName); + } + else + { + deobField = InjectUtil.findFieldDeep(deobTarget, hookName); + } + + assert mixinMethod.isStatic() == deobField.isStatic() : "Mixin method isn't static but deob has a static method named the same as the hook, and I was too lazy to do something about this bug"; + + final Number getter = DeobAnnotations.getObfuscatedGetter(deobField); + final Field obField = inject.toVanilla(deobField); + + final HookInfo info = new HookInfo(targetClass.getPoolClass(), mixinMethod, before, getter); + + hooked.put(obField, info); + } + } + } + + private void injectHooks() + { + Execution e = new Execution(inject.getVanilla()); + e.populateInitialMethods(); + + Set done = new HashSet<>(); + Set doneIh = new HashSet<>(); + + e.addExecutionVisitor(( + InstructionContext ic) -> + { + Instruction i = ic.getInstruction(); + Instructions ins = i.getInstructions(); + Code code = ins.getCode(); + Method method = code.getMethod(); + + if (method.getName().equals(CLINIT)) + return; + + if (!(i instanceof SetFieldInstruction)) + return; + + if (!done.add(i)) + return; + + SetFieldInstruction sfi = (SetFieldInstruction) i; + Field fieldBeingSet = sfi.getMyField(); + + if (fieldBeingSet == null) + return; + + HookInfo hookInfo = hooked.get(fieldBeingSet); + if (hookInfo == null) + return; + + log.trace("Found injection location for hook {} at instruction {}", hookInfo.method.getName(), sfi); + ++injectedHooks; + + StackContext value = ic.getPops().get(0); + + StackContext objectStackContext = null; + if (sfi instanceof PutField) + objectStackContext = ic.getPops().get(1); + + int idx = ins.getInstructions().indexOf(sfi); + assert idx != -1; + + try + { + if (hookInfo.before) + injectCallbackBefore(ins, idx, hookInfo, null, objectStackContext, value); + else + // idx + 1 to insert after the set + injectCallback(ins, idx + 1, hookInfo, null, objectStackContext); + } + catch (InjectException ex) + { + throw new RuntimeException(ex); + } + }); + + // these look like: + // getfield + // iload_0 + // iconst_0 + // iastore + e.addExecutionVisitor((InstructionContext ic) -> + { + Instruction i = ic.getInstruction(); + Instructions ins = i.getInstructions(); + Code code = ins.getCode(); + Method method = code.getMethod(); + + if (method.getName().equals(CLINIT)) + return; + + if (!(i instanceof ArrayStore)) + return; + + if (!doneIh.add(i)) + return; + + ArrayStore as = (ArrayStore) i; + + Field fieldBeingSet = as.getMyField(ic); + if (fieldBeingSet == null) + return; + + HookInfo hookInfo = hooked.get(fieldBeingSet); + if (hookInfo == null) + return; + + StackContext value = ic.getPops().get(0); + StackContext index = ic.getPops().get(1); + + StackContext arrayReference = ic.getPops().get(2); + InstructionContext arrayReferencePushed = arrayReference.getPushed(); + + StackContext objectStackContext = null; + if (arrayReferencePushed.getInstruction().getType() == InstructionType.GETFIELD) + objectStackContext = arrayReferencePushed.getPops().get(0); + + // inject hook after 'i' + log.debug("[DEBUG] Found array injection location for hook {} at instruction {}", hookInfo.method.getName(), i); + ++injectedHooks; + + int idx = ins.getInstructions().indexOf(i); + assert idx != -1; + + try + { + if (hookInfo.before) + injectCallbackBefore(ins, idx, hookInfo, index, objectStackContext, value); + else + injectCallback(ins, idx + 1, hookInfo, index, objectStackContext); + } + catch (InjectException ex) + { + throw new RuntimeException(ex); + } + }); + + e.run(); + } + + private void injectCallbackBefore(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext object, StackContext value) + { + Signature signature = hookInfo.method.getDescriptor(); + Type methodArgumentType = signature.getTypeOfArg(0); + + if (!hookInfo.method.isStatic()) + { + if (object == null) + throw new InjectException("null object"); + + ins.getInstructions().add(idx++, new Dup(ins)); // dup value + idx = recursivelyPush(ins, idx, object); + ins.getInstructions().add(idx++, new Swap(ins)); + + if (hookInfo.getter instanceof Integer) + { + ins.getInstructions().add(idx++, new LDC(ins, hookInfo.getter)); + ins.getInstructions().add(idx++, new IMul(ins)); + } + else if (hookInfo.getter instanceof Long) + { + ins.getInstructions().add(idx++, new LDC(ins, hookInfo.getter)); + ins.getInstructions().add(idx++, new LMul(ins)); + } + } + else + { + ins.getInstructions().add(idx++, new Dup(ins)); // dup value + } + + if (!value.type.equals(methodArgumentType)) + { + CheckCast checkCast = new CheckCast(ins); + checkCast.setType(methodArgumentType); + ins.getInstructions().add(idx++, checkCast); + } + if (index != null) + { + idx = recursivelyPush(ins, idx, index); + } + + Instruction invoke = hookInfo.getInvoke(ins); + ins.getInstructions().add(idx, invoke); + } + + private int recursivelyPush(Instructions ins, int idx, StackContext sctx) + { + InstructionContext ctx = sctx.getPushed(); + if (ctx.getInstruction() instanceof DupInstruction) + { + DupInstruction dupInstruction = (DupInstruction) ctx.getInstruction(); + sctx = dupInstruction.getOriginal(sctx); + ctx = sctx.getPushed(); + } + + for (StackContext s : Lists.reverse(ctx.getPops())) + idx = recursivelyPush(ins, idx, s); + + ins.getInstructions().add(idx++, ctx.getInstruction().clone()); + return idx; + } + + private void injectCallback(Instructions ins, int idx, HookInfo hookInfo, StackContext index, StackContext objectPusher) + { + if (!hookInfo.method.isStatic()) + { + if (objectPusher == null) + throw new InjectException("Null object pusher"); + + idx = recursivelyPush(ins, idx, objectPusher); + } + + if (index != null) + idx = recursivelyPush(ins, idx, index); + else + ins.getInstructions().add(idx++, new LDC(ins, -1)); + + Instruction invoke = hookInfo.getInvoke(ins); + ins.getInstructions().add(idx, invoke); + } + + @AllArgsConstructor + static class HookInfo + { + final Class targetClass; + final Method method; + final boolean before; + final Number getter; + + Instruction getInvoke(Instructions instructions) + { + return InjectUtil.createInvokeFor( + instructions, + new net.runelite.asm.pool.Method( + targetClass, + method.getName(), + method.getDescriptor() + ), + method.isStatic() + ); + } + } + + private static boolean isBefore(Annotation a) + { + Object val = a.get("before"); + return val instanceof Boolean && (Boolean) val; + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java new file mode 100644 index 0000000000..1ca49ac707 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javax.inject.Provider; +import net.runelite.asm.Annotation; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import net.runelite.asm.signature.Signature; + +public class InjectHookMethod extends AbstractInjector +{ + private static final Type METHODHOOK = new Type("Lnet/runelite/api/mixins/MethodHook;"); + private final Map, List> mixinTargets; + + private int injected = 0; + + InjectHookMethod(final InjectData inject, final Map, List> mixinTargets) + { + super(inject); + this.mixinTargets = mixinTargets; + } + + @Override + public void inject() + { + for (Map.Entry, List> entry : mixinTargets.entrySet()) + injectMethods(entry.getKey(), entry.getValue()); + + log.info("[INFO] Injected {} method hooks", injected); + } + + private void injectMethods(Provider mixinProvider, List targetClasses) + { + final ClassFile mixinClass = mixinProvider.get(); + + for (ClassFile targetClass : targetClasses) + { + for (Method mixinMethod : mixinClass.getMethods()) + { + final Annotation methodHook = mixinMethod.findAnnotation(METHODHOOK); + if (methodHook == null) + continue; + + if (!mixinMethod.getDescriptor().isVoid()) + throw new InjectException("Method hook " + mixinMethod.getPoolMethod() + " doesn't have void return type"); + + final String hookName = methodHook.getValueString(); + final boolean end = isEnd(methodHook); + + final ClassFile deobTarget = inject.toDeob(targetClass.getName()); + final Signature deobSig = InjectUtil.apiToDeob(inject, mixinMethod.getDescriptor()); + final boolean notStatic = !mixinMethod.isStatic(); + final Method targetMethod = InjectUtil.findMethod(inject, hookName, deobTarget.getName(), sig -> InjectUtil.argsMatch(sig, deobSig), notStatic, false); + + final net.runelite.asm.pool.Method hookMethod = new net.runelite.asm.pool.Method( + targetClass.getPoolClass(), + mixinMethod.getName(), + mixinMethod.getDescriptor() + ); + + inject(targetMethod, hookMethod, end); + + log.debug("[DEBUG] Injected method hook {} in {}", hookMethod, targetMethod); + ++injected; + } + } + } + + private void inject(final Method method, final net.runelite.asm.pool.Method hookMethod, boolean end) + { + final Instructions ins = method.getCode().getInstructions(); + final ListIterator it; + + if (end) + { + it = ins.listIterator(ins.size()); + while (it.hasPrevious()) + if (it.previous() instanceof ReturnInstruction) + insertVoke(method, hookMethod, it); + + return; + } + + it = ins.listIterator(); + if (method.getName().equals("")) + { + while (it.hasNext()) + if (it.next() instanceof InvokeSpecial) + break; + + assert it.hasNext() : "Constructor without invokespecial"; + } + + insertVoke(method, hookMethod, it); + } + + private void insertVoke(final Method method, final net.runelite.asm.pool.Method hookMethod, ListIterator iterator) + { + final Instructions instructions = method.getCode().getInstructions(); + int varIdx = 0; + + if (!method.isStatic()) + iterator.add(new ALoad(instructions, varIdx++)); + + for (Type type : hookMethod.getType().getArguments()) + { + iterator.add( + InjectUtil.createLoadForTypeIndex( + instructions, + type, + varIdx + ) + ); + + varIdx += type.getSize(); + } + + iterator.add( + InjectUtil.createInvokeFor( + instructions, + hookMethod, + method.isStatic() + ) + ); + } + + private static boolean isEnd(Annotation annotation) + { + Object val = annotation.get("end"); + return val instanceof Boolean && (Boolean) val; + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/Injector.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/Injector.java new file mode 100644 index 0000000000..a3d9048e47 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/Injector.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injectors; + +import net.runelite.asm.Named; + +public interface Injector extends Named +{ + /** + * Where all the injection should be done + */ + void inject(); + + /** + * Get a name the injector is going to be referred to in logging + */ + default String getName() + { + return this.getClass().getSimpleName(); + } + + /** + * Called before inject, AbstractInjector currently uses it to start a stopwatch + */ + void start(); + + /** + * Gets a message logged at quiet level when the injector ends + */ + String getCompletionMsg(); +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java new file mode 100644 index 0000000000..e96c0393cd --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injectors; + +import com.openosrs.injector.injection.InjectData; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Interfaces; +import net.runelite.asm.pool.Class; +import net.runelite.deob.DeobAnnotations; +import static com.openosrs.injector.rsapi.RSApi.API_BASE; + +public class InterfaceInjector extends AbstractInjector +{ + private int implemented = 0; + + public InterfaceInjector(InjectData inject) + { + super(inject); + } + + public void inject() + { + // forEachPair performs actions on a deob-vanilla pair, which is what's needed here + inject.forEachPair(this::injectInterface); + + log.info("[INFO] Injected {} interfaces", implemented); + } + + private void injectInterface(final ClassFile deobCf, final ClassFile vanillaCf) + { + final String impls = DeobAnnotations.getImplements(deobCf); + + if (impls == null) + return; + + final String fullName = API_BASE + impls; + if (!inject.getRsApi().hasClass(fullName)) + { + log.debug("[DEBUG] Class {} implements nonexistent interface {}, skipping interface injection", + deobCf.getName(), + fullName + ); + + return; + } + + final Interfaces interfaces = vanillaCf.getInterfaces(); + interfaces.addInterface(new Class(fullName)); + implemented++; + + inject.addToDeob(fullName, deobCf); + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java new file mode 100644 index 0000000000..502d09f67d --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.inject.Provider; +import lombok.Value; +import net.runelite.asm.Annotation; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotated; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.FieldInstruction; +import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction; +import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction; +import net.runelite.asm.attributes.code.instruction.types.LVTInstruction; +import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction; +import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction; +import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.ANewArray; +import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.GetField; +import net.runelite.asm.attributes.code.instructions.InvokeDynamic; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.Pop; +import net.runelite.asm.attributes.code.instructions.PutField; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import net.runelite.deob.util.JarUtil; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; + +public class MixinInjector extends AbstractInjector +{ + private static final Type COPY = new Type("Lnet/runelite/api/mixins/Copy;"); + private static final Type INJECT = new Type("Lnet/runelite/api/mixins/Inject;"); + private static final Type MIXIN = new Type("Lnet/runelite/api/mixins/Mixin;"); + private static final Type MIXINS = new Type("Lnet/runelite/api/mixins/Mixins;"); + private static final Type REPLACE = new Type("Lnet/runelite/api/mixins/Replace;"); + private static final Type SHADOW = new Type("Lnet/runelite/api/mixins/Shadow;"); + private static final String ASSERTION_FIELD = "$assertionsDisabled"; + private static final String MIXIN_BASE = "net/runelite/mixins/"; + + private final Map injectedFields = new HashMap<>(); + private final Map shadowFields = new HashMap<>(); + private int copied = 0, replaced = 0, injected = 0; + + public MixinInjector(InjectData inject) + { + super(inject); + } + + @Override + public void inject() + { + final Map, List> mixinTargets = initTargets(); + inject(mixinTargets); + } + + @VisibleForTesting + void inject(Map, List> mixinTargets) + { + for (Map.Entry, List> entry : mixinTargets.entrySet()) + { + System.out.println(entry.getKey().get().getName()); + injectFields(entry.getKey(), entry.getValue()); + } + + log.info("[INFO] Injected {} fields", injectedFields.size()); + + for (Map.Entry, List> entry : mixinTargets.entrySet()) + { + findShadowFields(entry.getKey()); + } + + log.info("[INFO] Shadowed {} fields", shadowFields.size()); + + for (Map.Entry, List> entry : mixinTargets.entrySet()) + { + injectMethods(entry.getKey(), entry.getValue()); + } + + log.info("[INFO] Injected {}, copied {}, replaced {} methods", injected, copied, replaced); + + inject.runChildInjector(new InjectHook(inject, mixinTargets)); + + inject.runChildInjector(new InjectHookMethod(inject, mixinTargets)); + } + + private Map, List> initTargets() + { + ImmutableMap.Builder, List> builder = ImmutableMap.builder(); + + try + { + for (ClassFile mixinClass : inject.getMixins()) + { + System.out.println(mixinClass.getName()); + final List ret = getMixins(mixinClass); + builder.put( + (ret.size() > 1 ? mixinProvider(mixinClass) : () -> mixinClass), + ret + ); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + return builder.build(); + } + + private void injectFields(Provider mixinProvider, List targetClasses) + { + try + { + final ClassFile mixinClass = mixinProvider.get(); + + for (final ClassFile targetClass : targetClasses) + { + for (Field field : mixinClass.getFields()) + { + if (field.findAnnotation(INJECT) == null && + (!ASSERTION_FIELD.equals(field.getName()) || targetClass.findField(ASSERTION_FIELD, Type.BOOLEAN) != null)) + { + continue; + } + + Field copy = new Field(targetClass, field.getName(), field.getType()); + copy.setAccessFlags(field.getAccessFlags()); + copy.setPublic(); + copy.setValue(field.getValue()); + + for (Map.Entry e : field.getAnnotations().entrySet()) + if (!e.getKey().toString().startsWith("Lnet/runelite/api/mixins")) + copy.addAnnotation(e.getValue()); + + targetClass.addField(copy); + + if (injectedFields.containsKey(field.getName()) && !ASSERTION_FIELD.equals(field.getName())) + throw new InjectException("Duplicate field: " + field.getName()); + + injectedFields.put(field.getName(), copy); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + + private void findShadowFields(Provider mixinProvider) + { + final ClassFile mixinClass = mixinProvider.get(); + + for (final Field field : mixinClass.getFields()) + { + Annotation shadow = field.findAnnotation(SHADOW); + + if (shadow == null) + continue; + + if (!field.isStatic()) + throw new InjectException("Shadowed fields must be static"); + + String shadowed = shadow.getValueString(); + + Field targetField = injectedFields.get(shadowed); + Number getter = null; + + if (targetField == null) + { + final Field deobTargetField = InjectUtil.findStaticField(inject, shadowed, null, InjectUtil.apiToDeob(inject, field.getType())); + targetField = inject.toVanilla(deobTargetField); + + getter = DeobAnnotations.getObfuscatedGetter(deobTargetField); + } + + if ((targetField.getAccessFlags() & Opcodes.ACC_PRIVATE) != 0) + throw new InjectException("Shadowed fields can't be private"); + + shadowFields.put( + field.getPoolField(), + new ShadowField(targetField, getter) + ); + } + } + + private void injectMethods(Provider mixinProvider, List targetClasses) + { + for (ClassFile targetClass : targetClasses) + { + final ClassFile mixinClass = mixinProvider.get(); + + // Keeps mappings between methods annotated with @Copy -> the copied method within the vanilla pack + Map copiedMethods = new HashMap<>(); + + // Handle the copy mixins first, so all other mixins know of the copies + for (Method mixinMethod : mixinClass.getMethods()) + { + Annotation copyA = mixinMethod.findAnnotation(COPY); + if (copyA == null) + continue; + String copiedName = copyA.getValueString(); + + Signature deobSig = InjectUtil.apiToDeob(inject, mixinMethod.getDescriptor()); + boolean notStat = !mixinMethod.isStatic(); + + Method deobSourceMethod = InjectUtil.findMethod(inject, copiedName, inject.toDeob(targetClass.getName()).getName(), deobSig::equals, notStat, true); + + if (mixinMethod.isStatic() != deobSourceMethod.isStatic()) + throw new InjectException("Mixin method " + mixinMethod + " should be " + (deobSourceMethod.isStatic() ? "static" : "non-static")); + + // The actual method we're copying, including code etc + Method sourceMethod = inject.toVanilla(deobSourceMethod); + + if (mixinMethod.getDescriptor().size() > sourceMethod.getDescriptor().size()) + throw new InjectException("Mixin methods cannot have more parameters than their corresponding ob method"); + + Method copy = new Method(targetClass, "copy$" + copiedName, sourceMethod.getDescriptor()); + moveCode(copy, sourceMethod.getCode()); + copy.setAccessFlags(sourceMethod.getAccessFlags()); + copy.setPublic(); + copy.getExceptions().getExceptions().addAll(sourceMethod.getExceptions().getExceptions()); + for (var a : sourceMethod.getAnnotations().values()) + copy.addAnnotation(a); + targetClass.addMethod(copy); + ++copied; + + /* + * If the desc for the mixin method and the desc for the ob method + * are the same in length, assume that the mixin method is taking + * care of the garbage parameter itself. + */ + boolean hasGarbageValue = mixinMethod.getDescriptor().size() != sourceMethod.getDescriptor().size() + && deobSourceMethod.getDescriptor().size() < copy.getDescriptor().size(); + + copiedMethods.put(mixinMethod.getPoolMethod(), new CopiedMethod(copy, !hasGarbageValue ? null : Integer.valueOf(DeobAnnotations.getDecoder(deobSourceMethod)))); + } + + // Handle the rest of the mixin types + for (Method mixinMethod : mixinClass.getMethods()) + { + boolean isClinit = "".equals(mixinMethod.getName()); + boolean isInit = "".equals(mixinMethod.getName()); + boolean hasInject = mixinMethod.findAnnotation(INJECT) != null; + + // You can't annotate clinit, so its always injected + if ((hasInject && isInit) || isClinit) + { + if (!"()V".equals(mixinMethod.getDescriptor().toString())) + throw new InjectException("Injected constructors cannot have arguments"); + + Method[] originalMethods = targetClass.getMethods().stream() + .filter(m -> m.getName().equals(mixinMethod.getName())) + .toArray(Method[]::new); + + String name = mixinMethod.getName(); + + // If there isn't a already just inject ours, otherwise rename it + // This is always true for + if (originalMethods.length > 0) + name = "rl$$" + (isInit ? "init" : "clinit"); + + String numberlessName = name; + for (int i = 1; targetClass.findMethod(name, mixinMethod.getDescriptor()) != null; i++) + name = numberlessName + i; + + Method copy = new Method(targetClass, name, mixinMethod.getDescriptor()); + moveCode(copy, mixinMethod.getCode()); + copy.setAccessFlags(mixinMethod.getAccessFlags()); + copy.setPrivate(); + assert mixinMethod.getExceptions().getExceptions().isEmpty(); + + // Remove the call to the superclass's ctor + if (isInit) + { + Instructions instructions = copy.getCode().getInstructions(); + ListIterator listIter = instructions.listIterator(); + while (listIter.hasNext()) + { + Instruction instr = listIter.next(); + if (instr instanceof InvokeSpecial) + { + InvokeSpecial invoke = (InvokeSpecial) instr; + assert invoke.getMethod().getName().equals(""); + listIter.remove(); + int pops = invoke.getMethod().getType().getArguments().size() + 1; + + for (int i = 0; i < pops; i++) + listIter.add(new Pop(instructions)); + + break; + } + } + } + + setOwnersToTargetClass(mixinClass, targetClass, copy, copiedMethods); + targetClass.addMethod(copy); + + // Call our method at the return point of the matching method(s) + for (Method om : originalMethods) + { + Instructions instructions = om.getCode().getInstructions(); + ListIterator listIter = instructions.listIterator(); + + while (listIter.hasNext()) + { + Instruction instr = listIter.next(); + if (instr instanceof ReturnInstruction) + { + listIter.previous(); + if (isInit) + { + listIter.add(new ALoad(instructions, 0)); + listIter.add(new InvokeSpecial(instructions, copy.getPoolMethod())); + } + else if (isClinit) + { + listIter.add(new InvokeStatic(instructions, copy.getPoolMethod())); + } + listIter.next(); + } + } + } + + log.debug("[DEBUG] Injected mixin method {} to {}", copy, targetClass); + ++injected; + } + else if (hasInject) + { + // Make sure the method doesn't invoke copied methods + for (Instruction i : mixinMethod.getCode().getInstructions()) + { + if (i instanceof InvokeInstruction) + { + InvokeInstruction ii = (InvokeInstruction) i; + + if (copiedMethods.containsKey(ii.getMethod())) + { + throw new InjectException("Injected methods cannot invoke copied methods"); + } + } + } + + Method copy = new Method(targetClass, mixinMethod.getName(), mixinMethod.getDescriptor()); + moveCode(copy, mixinMethod.getCode()); + copy.setAccessFlags(mixinMethod.getAccessFlags()); + copy.setPublic(); + assert mixinMethod.getExceptions().getExceptions().isEmpty(); + + setOwnersToTargetClass(mixinClass, targetClass, copy, copiedMethods); + + targetClass.addMethod(copy); + + log.debug("[DEBUG] Injected mixin method {} to {}", copy, targetClass); + ++injected; + } + else if (mixinMethod.findAnnotation(REPLACE) != null) + { + Annotation replaceAnnotation = mixinMethod.findAnnotation(REPLACE); + String replacedName = replaceAnnotation.getValueString(); + + ClassFile deobClass = inject.toDeob(targetClass.getName()); + Method deobMethod = findDeobMatching(deobClass, mixinMethod, replacedName); + + if (deobMethod == null) + { + throw new InjectException("Failed to find the deob method " + replacedName + " for mixin " + mixinClass); + } + + if (mixinMethod.isStatic() != deobMethod.isStatic()) + { + throw new InjectException("Mixin method " + mixinMethod + " should be " + + (deobMethod.isStatic() ? "static" : "non-static")); + } + + String obReplacedName = InjectUtil.getObfuscatedName(deobMethod); + Signature obMethodSignature = deobMethod.getObfuscatedSignature(); + + // Find the vanilla class where the method to copy is in + ClassFile obCf = inject.toVanilla(deobMethod.getClassFile()); + + Method obMethod = obCf.findMethod(obReplacedName, obMethodSignature); + assert obMethod != null : "obfuscated method " + obReplacedName + obMethodSignature + " does not exist"; + + if (mixinMethod.getDescriptor().size() > obMethod.getDescriptor().size()) + { + throw new InjectException("Mixin methods cannot have more parameters than their corresponding ob method"); + } + + Type returnType = mixinMethod.getDescriptor().getReturnValue(); + Type deobReturnType = InjectUtil.apiToDeob(inject, returnType); + if (!returnType.equals(deobReturnType)) + { + ClassFile deobReturnTypeClassFile = inject.getDeobfuscated() + .findClass(deobReturnType.getInternalName()); + if (deobReturnTypeClassFile != null) + { + ClassFile obReturnTypeClass = inject.toVanilla(deobReturnTypeClassFile); + + Instructions instructions = mixinMethod.getCode().getInstructions(); + ListIterator listIter = instructions.listIterator(); + while (listIter.hasNext()) + { + Instruction instr = listIter.next(); + if (instr instanceof ReturnInstruction) + { + listIter.previous(); + CheckCast checkCast = new CheckCast(instructions); + checkCast.setType(new Type(obReturnTypeClass.getName())); + listIter.add(checkCast); + listIter.next(); + } + } + } + } + + moveCode(obMethod, mixinMethod.getCode()); + + boolean hasGarbageValue = mixinMethod.getDescriptor().size() != obMethod.getDescriptor().size() + && deobMethod.getDescriptor().size() < obMethodSignature.size(); + + if (hasGarbageValue) + { + int garbageIndex = obMethod.isStatic() + ? obMethod.getDescriptor().size() - 1 + : obMethod.getDescriptor().size(); + + /* + If the mixin method doesn't have the garbage parameter, + the compiler will have produced code that uses the garbage + parameter's local variable index for other things, + so we'll have to add 1 to all loads/stores to indices + that are >= garbageIndex. + */ + shiftLocalIndices(obMethod.getCode().getInstructions(), garbageIndex); + } + + setOwnersToTargetClass(mixinClass, targetClass, obMethod, copiedMethods); + + log.debug("[DEBUG] Replaced method {} with mixin method {}", obMethod, mixinMethod); + replaced++; + } + } + } + } + + private void moveCode(Method targetMethod, Code sourceCode) + { + Code newCode = new Code(targetMethod); + newCode.setMaxStack(sourceCode.getMaxStack()); + newCode.getInstructions().getInstructions().addAll(sourceCode.getInstructions().getInstructions()); + + // Update instructions for each instruction + for (Instruction i : newCode.getInstructions()) + i.setInstructions(newCode.getInstructions()); + + newCode.getExceptions().getExceptions().addAll(sourceCode.getExceptions().getExceptions()); + for (net.runelite.asm.attributes.code.Exception e : newCode.getExceptions().getExceptions()) + e.setExceptions(newCode.getExceptions()); + + targetMethod.setCode(newCode); + } + + private void setOwnersToTargetClass(ClassFile mixinCf, ClassFile cf, Method method, Map copiedMethods) + { + ListIterator iterator = method.getCode().getInstructions().listIterator(); + + while (iterator.hasNext()) + { + Instruction i = iterator.next(); + + if (i instanceof ANewArray) + { + Type type = ((ANewArray) i).getType_(); + ClassFile deobTypeClass = inject.toDeob(type.getInternalName()); + + if (deobTypeClass != null) + { + Type newType = new Type("L" + inject.toVanilla(deobTypeClass).getName() + ";"); + + ((ANewArray) i).setType(newType); + log.debug("[DEBUG] Replaced {} type {} with type {}", i, type, newType); + } + } + else if (i instanceof InvokeInstruction) + { + InvokeInstruction ii = (InvokeInstruction) i; + + CopiedMethod copiedMethod = copiedMethods.get(ii.getMethod()); + if (copiedMethod != null) + { + ii.setMethod(copiedMethod.copy.getPoolMethod()); + + // Pass through garbage value if the method has one + if (copiedMethod.garbage != null) + { + iterator.previous(); + iterator.add(new LDC(method.getCode().getInstructions(), copiedMethod.garbage)); + iterator.next(); + } + } + else if (ii.getMethod().getClazz().getName().equals(mixinCf.getName())) + { + ii.setMethod(new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(cf.getName()), + ii.getMethod().getName(), + ii.getMethod().getType() + )); + } + } + else if (i instanceof FieldInstruction) + { + FieldInstruction fi = (FieldInstruction) i; + + ShadowField shadowField = shadowFields.get(fi.getField()); + + if (shadowField != null) + { + Field shadowed = shadowField.targetField; + if (shadowField.obfuscatedGetter != null) + { + if (i instanceof SetFieldInstruction) + { + iterator.previous(); + InjectUtil.injectObfuscatedSetter(shadowField.obfuscatedGetter, i.getInstructions(), iterator::add); + iterator.next(); + } + else if (i instanceof GetFieldInstruction) + { + InjectUtil.injectObfuscatedGetter(shadowField.obfuscatedGetter, i.getInstructions(), iterator::add); + } + } + + fi.setField(shadowed.getPoolField()); + } + else if (fi.getField().getClazz().getName().equals(mixinCf.getName())) + { + fi.setField(new net.runelite.asm.pool.Field( + new net.runelite.asm.pool.Class(cf.getName()), + fi.getField().getName(), + fi.getField().getType() + )); + } + } + else if (i instanceof PushConstantInstruction) + { + PushConstantInstruction pi = (PushConstantInstruction) i; + + if (mixinCf.getPoolClass().equals(pi.getConstant())) + pi.setConstant(cf.getPoolClass()); + } + + verify(mixinCf, i); + } + } + + private void verify(ClassFile mixinCf, Instruction i) + { + if (i instanceof FieldInstruction) + { + FieldInstruction fi = (FieldInstruction) i; + + if (fi.getField().getClazz().getName().equals(mixinCf.getName())) + { + if (i instanceof PutField || i instanceof GetField) + throw new InjectException("Access to non static member field of mixin"); + + Field field = fi.getMyField(); + if (field != null && !field.isPublic()) + throw new InjectException("Static access to non public field " + field); + } + } + else if (i instanceof InvokeStatic) + { + InvokeStatic is = (InvokeStatic) i; + + if (is.getMethod().getClazz() != mixinCf.getPoolClass() + && is.getMethod().getClazz().getName().startsWith(MIXIN_BASE)) + throw new InjectException("Invoking static methods of other mixins is not supported"); + } + else if (i instanceof InvokeDynamic) + // RS classes don't verify under java 7+ due to the + // super() invokespecial being inside of a try{} + throw new InjectException("Injected bytecode must be Java 6 compatible"); + } + + private Method findDeobMatching(ClassFile deobClass, Method mixinMethod, String deobName) + { + List matching = new ArrayList<>(); + + for (Method method : deobClass.getMethods()) + { + if (!deobName.equals(method.getName())) + continue; + + if (InjectUtil.apiToDeobSigEquals(inject, method.getDescriptor(), mixinMethod.getDescriptor())) + matching.add(method); + } + + if (matching.size() > 1) + // this happens when it has found several deob methods for some mixin method, + // to get rid of the error, refine your search by making your mixin method have more parameters + throw new InjectException("There are several matching methods when there should only be one"); + else if (matching.size() == 1) + return matching.get(0); + + return inject.getDeobfuscated().findStaticMethod(deobName); + } + + private void shiftLocalIndices(Instructions instructions, int startIdx) + { + for (Instruction i : instructions) + { + if (i instanceof LVTInstruction) + { + LVTInstruction lvti = (LVTInstruction) i; + + if (lvti.getVariableIndex() >= startIdx) + { + lvti.setVariableIndex(lvti.getVariableIndex() + 1); + } + } + } + } + + @SuppressWarnings("unchecked") + private List getMixins(Annotated from) + { + final Annotation mixin = from.findAnnotation(MIXIN); + if (mixin != null) + { + return List.of(InjectUtil.getVanillaClassFromAnnotationString(inject, mixin)); + } + final Annotation mixins = from.findAnnotation(MIXINS); + if (mixins != null) + { + return ((List) mixins.getValue()).stream() + .map(mix -> InjectUtil.getVanillaClassFromAnnotationString(inject, mix)) + .collect(Collectors.toUnmodifiableList()); + } + throw new IllegalArgumentException("No MIXIN or MIXINS found on " + from.toString()); + } + + @Value + private static class CopiedMethod + { + @Nonnull Method copy; + @Nullable Integer garbage; + } + + @Value + private static class ShadowField + { + @Nonnull Field targetField; + @Nullable Number obfuscatedGetter; + } + + private static Provider mixinProvider(ClassFile mixin) + { + return new Provider<>() + { + byte[] bytes = null; + + @Override + public ClassFile get() + { + if (bytes != null) + return JarUtil.loadClass(bytes); + + bytes = JarUtil.writeClass(mixin.getGroup(), mixin); + return mixin; + } + }; + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java new file mode 100644 index 0000000000..e5a9c4e0d9 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.injectors.rsapi.InjectGetter; +import com.openosrs.injector.injectors.rsapi.InjectInvoke; +import com.openosrs.injector.injectors.rsapi.InjectSetter; +import com.openosrs.injector.rsapi.RSApiClass; +import com.openosrs.injector.rsapi.RSApiMethod; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import net.runelite.asm.Type; +import net.runelite.asm.attributes.Annotated; +import net.runelite.asm.signature.Signature; +import net.runelite.deob.DeobAnnotations; +import static com.openosrs.injector.rsapi.RSApi.API_BASE; + +public class RSApiInjector extends AbstractInjector +{ + private final Map> retryFields = new HashMap<>(); + private int get = 0, set = 0, voke = 0; + + public RSApiInjector(InjectData inject) + { + super(inject); + } + + public void inject() + { + for (final ClassFile deobClass : inject.getDeobfuscated()) + { + final RSApiClass implementingClass = inject.getRsApi().findClass(API_BASE + deobClass.getName()); + + injectFields(deobClass, implementingClass); + injectMethods(deobClass, implementingClass); + } + + retryFailures(); + + log.info("[INFO] Injected {} getters, {} setters, and {} invokers", get, set, voke); + } + + private void injectFields(ClassFile deobClass, RSApiClass implementingClass) + { + for (Field deobField : deobClass.getFields()) + { + final List matching = findImportsFor(deobField, deobField.isStatic(), implementingClass); + + if (matching == null) + continue; + + final Type deobType = deobField.getType(); + + // We're dealing with a field here, so only getter/setter methods match + ListIterator it = matching.listIterator(); + while (it.hasNext()) + { + RSApiMethod apiMethod = it.next(); + + if (apiMethod.isInjected()) + { + it.remove(); + continue; + } + + // Check if there's a field with the same exported name on the api target class itself, + // as that should come before random static fields. + if (deobField.isStatic()) + { + ClassFile deobApiTarget = InjectUtil.deobFromApiMethod(inject, apiMethod); + + if (deobApiTarget != deobClass && + deobApiTarget.findField(deobField.getName()) != null) + { + it.remove(); + continue; + } + } + + final Signature sig = apiMethod.getSignature(); + + if (sig.isVoid()) + { + if (sig.size() == 1) + { + Type type = InjectUtil.apiToDeob(inject, sig.getTypeOfArg(0)); + if (deobType.equals(type)) + { + continue; + } + } + } + else if (sig.size() == 0) + { + Type type = InjectUtil.apiToDeob(inject, sig.getReturnValue()); + if (deobType.equals(type)) + { + continue; + } + } + + it.remove(); + } + + if (matching.size() == 0) + { + continue; + } + else if (matching.size() > 2) + { + retryFields.put(deobField, new ArrayList<>(matching)); + continue; + } + + final Field vanillaField = inject.toVanilla(deobField); + final Number getter = DeobAnnotations.getObfuscatedGetter(deobField); + + if (deobField.isStatic() != vanillaField.isStatic()) // Can this even happen + throw new InjectException("Something went horribly wrong, and this should honestly never happen, but you never know. Btw it's the static-ness"); + + inject(matching, deobField, vanillaField, getter); + } + } + + private void injectMethods(ClassFile deobClass, RSApiClass implementingClass) + { + for (Method deobMethod : deobClass.getMethods()) + { + final List matching = findImportsFor(deobMethod, deobMethod.isStatic(), implementingClass); + + if (matching == null) + continue; + + final Signature deobSig = deobMethod.getDescriptor(); + + ListIterator it = matching.listIterator(); + while (it.hasNext()) + { + final RSApiMethod apiMethod = it.next(); + + // Check if there's a method with the same exported name on the api target class itself, + // as that should come before random static methods. + if (deobMethod.isStatic()) + { + ClassFile deobApiTarget = InjectUtil.deobFromApiMethod(inject, apiMethod); + + if (deobApiTarget != deobClass && + deobApiTarget.findMethod(deobMethod.getName()) != null) + { + it.remove(); + continue; + } + } + + final Signature apiSig = apiMethod.getSignature(); + + if (apiMethod.isInjected() + || !InjectUtil.apiToDeobSigEquals(inject, deobSig, apiSig)) + { + it.remove(); + } + } + + if (matching.size() == 1) + { + final RSApiMethod apiMethod = matching.get(0); + + final ClassFile targetClass = InjectUtil.vanillaFromApiMethod(inject, apiMethod); + final Method vanillaMethod = inject.toVanilla(deobMethod); + final String garbage = DeobAnnotations.getDecoder(deobMethod); + log.debug("[DEBUG] Injecting invoker {} for {} into {}", apiMethod.getMethod(), vanillaMethod.getPoolMethod(), targetClass.getPoolClass()); + InjectInvoke.inject(targetClass, apiMethod, vanillaMethod, garbage); + ++voke; + apiMethod.setInjected(true); + } + else if (matching.size() != 0) + throw new InjectException("Multiple api imports matching method " + deobMethod.getPoolMethod()); + } + } + + private void retryFailures() + { + for (Map.Entry> entry : retryFields.entrySet()) + { + final List matched = entry.getValue(); + final Field deobField = entry.getKey(); + + matched.removeIf(RSApiMethod::isInjected); + + if (matched.size() > 2) + throw new InjectException("More than 2 imported api methods for field " + deobField.getPoolField()); + + final Field vanillaField = inject.toVanilla(deobField); + final Number getter = DeobAnnotations.getObfuscatedGetter(deobField); + + inject(matched, deobField, vanillaField, getter); + } + } + + private List findImportsFor(Annotated object, boolean statik, RSApiClass implemented) + { + final String exportedName = InjectUtil.getExportedName(object); + if (exportedName == null) + return null; + + final List matching = new ArrayList<>(); + + if (statik) + { + for (RSApiClass api : inject.getRsApi()) + { + api.fetchImported(matching, exportedName); + } + } + else if (implemented != null) + { + implemented.fetchImported(matching, exportedName); + } + + return matching; + } + + private void inject(List matched, Field field, Field targetField, Number getter) + { + for (RSApiMethod apiMethod : matched) + { + final ClassFile targetClass = InjectUtil.vanillaFromApiMethod(inject, apiMethod); + apiMethod.setInjected(true); + + if (apiMethod.getSignature().isVoid()) + { + ++set; + log.debug("[DEBUG] Injecting setter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass()); + InjectSetter.inject( + targetClass, + apiMethod, + targetField, + getter + ); + } + else + { + ++get; + log.debug("[DEBUG] Injecting getter {} for {} into {}", apiMethod.getMethod(), field.getPoolField(), targetClass.getPoolClass()); + InjectGetter.inject( + targetClass, + apiMethod, + targetField, + getter + ); + } + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java new file mode 100644 index 0000000000..578b30b005 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2019, Lucas + * Copyright (c) 2020, ThatGamerBlue + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injectors.raw; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.injectors.AbstractInjector; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.InstructionType; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.Label; +import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction; +import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction; +import net.runelite.asm.attributes.code.instructions.ALoad; +import net.runelite.asm.attributes.code.instructions.BiPush; +import net.runelite.asm.attributes.code.instructions.GetStatic; +import net.runelite.asm.attributes.code.instructions.IAnd; +import net.runelite.asm.attributes.code.instructions.IfACmpEq; +import net.runelite.asm.attributes.code.instructions.IfACmpNe; +import net.runelite.asm.attributes.code.instructions.IfICmpNe; +import net.runelite.asm.attributes.code.instructions.IfNe; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.attributes.code.instructions.Return; +import net.runelite.asm.pool.Field; + +public class AddPlayerToMenu extends AbstractInjector +{ + public AddPlayerToMenu(InjectData inject) + { + super(inject); + } + + public void inject() + { + final Method addPlayerOptions = InjectUtil.findMethod(inject, "addPlayerToMenu"); + final net.runelite.asm.pool.Method shouldHideAttackOptionFor = + inject.getVanilla().findClass("client").findMethod("shouldHideAttackOptionFor").getPoolMethod(); + final net.runelite.asm.pool.Method shouldDrawMethod = + inject.getVanilla().findStaticMethod("shouldDraw").getPoolMethod(); + + try + { + injectSameTileFix(addPlayerOptions, shouldDrawMethod); + injectHideAttack(addPlayerOptions, shouldHideAttackOptionFor); + injectHideCast(addPlayerOptions, shouldHideAttackOptionFor); + } + catch (InjectException | AssertionError e) + { + log.warn("[WARN] HidePlayerAttacks failed, but as this doesn't mess up anything other than that functionality, we're carrying on", e); + } + } + + private void injectSameTileFix(Method addPlayerOptions, net.runelite.asm.pool.Method shouldDrawMethod) + { + // ALOAD 0 + // ICONST_0 + // INVOKESTATIC Scene.shouldDraw + // IFNE CONTINUE_LABEL if returned true then jump to continue + // RETURN + // CONTINUE_LABEL + // REST OF METHOD GOES HERE + Instructions insns = addPlayerOptions.getCode().getInstructions(); + Label CONTINUE_LABEL = new Label(insns); + List prependList = new ArrayList<>() + {{ + add(new ALoad(insns, 0)); + add(new LDC(insns, 0)); + add(new InvokeStatic(insns, shouldDrawMethod)); + add(new IfNe(insns, CONTINUE_LABEL)); + add(new Return(insns, InstructionType.RETURN)); + add(CONTINUE_LABEL); + }}; + int i = 0; + for (Instruction instruction : prependList) + { + insns.addInstruction(i++, instruction); + } + } + + private void injectHideAttack(Method addPlayerOptions, net.runelite.asm.pool.Method shouldHideAttackOptionFor) + { + final Field AttackOption_hidden = + InjectUtil.findField(inject, "AttackOption_hidden", "AttackOption").getPoolField(); + final Field attackOption = InjectUtil.findField(inject, "playerAttackOption", "Client").getPoolField(); + + // GETSTATIC GETSTATIC + // GETSTATIC GETSTATIC + // IFACMPEQ -> label continue IFACMPNE -> label whatever lets carry on + // MORE OBFUSCATION + + int injectIdx = -1; + Instruction labelIns = null; + Label label = null; + + Instructions ins = addPlayerOptions.getCode().getInstructions(); + Iterator iterator = ins.getInstructions().iterator(); + while (iterator.hasNext()) + { + Instruction i = iterator.next(); + if (!(i instanceof GetStatic)) + { + continue; + } + + Field field = ((GetStatic) i).getField(); + if (!field.equals(AttackOption_hidden) && !field.equals(attackOption)) + { + continue; + } + + i = iterator.next(); + if (!(i instanceof GetStatic)) + { + continue; + } + + field = ((GetStatic) i).getField(); + if (!field.equals(AttackOption_hidden) && !field.equals(attackOption)) + { + continue; + } + + i = iterator.next(); + if (!(i instanceof ComparisonInstruction && i instanceof JumpingInstruction)) + { + log.info("[INFO] You're not supposed to see this lol"); + continue; + } + + if (i instanceof IfACmpEq) + { + injectIdx = ins.getInstructions().indexOf(i) + 1; + label = ((IfACmpEq) i).getJumps().get(0); + } + else if (i instanceof IfACmpNe) + { + injectIdx = ins.getInstructions().indexOf(((IfACmpNe) i).getJumps().get(0)) + 1; + // We're gonna have to inject a extra label + labelIns = iterator.next(); + } + + break; + } + + if (injectIdx <= 0 || label == null && labelIns == null) + { + throw new InjectException("HidePlayerAttacks failed"); + } + + // Load the player + ALoad i1 = new ALoad(ins, 0); + // Get the boolean + InvokeStatic i2 = new InvokeStatic(ins, shouldHideAttackOptionFor); + + ins.addInstruction(injectIdx, i1); + ins.addInstruction(injectIdx + 1, i2); + + if (label == null) + { + label = ins.createLabelFor(labelIns); + ins.rebuildLabels(); + injectIdx = ins.getInstructions().indexOf(i2) + 1; + } + + // Compare n such + IfNe i3 = new IfNe(ins, label); + + ins.addInstruction(injectIdx, i3); + } + + private void injectHideCast(Method addPlayerOptions, net.runelite.asm.pool.Method shouldHideAttackOptionFor) + { + // LABEL before + // BIPUSH 8 + // LDC (garbage) + // GETSTATIC selectedSpellFlags + // IMUL + // BIPUSH 8 + // IAND + // IF_ICMPNE -> skip adding option + // + // <--- Inject call here + // <--- Inject comparison here (duh) + // + // add option n such + final Field flags = InjectUtil.findField(inject, "selectedSpellFlags", "Client").getPoolField(); + Instructions ins = addPlayerOptions.getCode().getInstructions(); + ListIterator iterator = ins.getInstructions().listIterator(); + boolean b1, b2, iAnd, getstatic; + b1 = b2 = iAnd = getstatic = false; + while (iterator.hasNext()) + { + Instruction i = iterator.next(); + + if (i instanceof Label) + { + b1 = b2 = iAnd = getstatic = false; + continue; + } + + if ((i instanceof BiPush) && (byte) ((BiPush) i).getConstant() == 8) + { + if (!b1) + { + b1 = true; + } + else if (!b2) + { + b2 = true; + } + else + { + throw new InjectException("3 bipushes? fucking mental, Hide spells failed btw"); + } + + continue; + } + + if (i instanceof IAnd) + { + iAnd = true; + continue; + } + + if (i instanceof GetStatic && ((GetStatic) i).getField().equals(flags)) + { + getstatic = true; + continue; + } + + if (!(i instanceof JumpingInstruction)) + { + if (b1 && b2 && iAnd && getstatic) + { + throw new InjectException("@ me in discord if this shit is broken lol, hide spells failed btw"); + } + continue; + } + + if (!(b1 && b2 && iAnd && getstatic)) + { + continue; + } + + Label target; + if (i instanceof IfICmpNe) + { + target = ((IfICmpNe) i).getJumps().get(0); + } + else + { + throw new InjectException("@ me in discord if this shit is broken lol, hide spells failed btw"); + } + + // Load the player + ALoad i1 = new ALoad(ins, 0); + // Get the boolean + InvokeStatic i2 = new InvokeStatic(ins, shouldHideAttackOptionFor); + // Compare n such + IfNe i3 = new IfNe(ins, target); + + iterator.add(i1); + iterator.add(i2); + iterator.add(i3); + return; + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java new file mode 100644 index 0000000000..762e269613 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + */ +package com.openosrs.injector.injectors.raw; + +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.injectors.AbstractInjector; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction; +import net.runelite.asm.attributes.code.instructions.ILoad; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.execution.Execution; +import net.runelite.asm.execution.InstructionContext; +import net.runelite.asm.execution.MethodContext; +import net.runelite.asm.execution.StackContext; +import net.runelite.asm.signature.Signature; +import static com.openosrs.injector.injection.InjectData.HOOKS; + +public class ClearColorBuffer extends AbstractInjector +{ + private static final net.runelite.asm.pool.Method CLEARBUFFER = new net.runelite.asm.pool.Method( + new net.runelite.asm.pool.Class(HOOKS), + "clearColorBuffer", + new Signature("(IIIII)V") + ); + + public ClearColorBuffer(InjectData inject) + { + super(inject); + } + + public void inject() + { + /* + * This class stops the client from basically painting everything black before the scene is drawn + */ + final Execution exec = new Execution(inject.getVanilla()); + + final net.runelite.asm.pool.Method fillRectPool = InjectUtil.findMethod(inject, "Rasterizer2D_fillRectangle", "Rasterizer2D", null).getPoolMethod(); + final Method drawEntities = InjectUtil.findMethod(inject, "drawEntities"); // XXX: should prob be called drawViewport? + + exec.addMethod(drawEntities); + exec.noInvoke = true; + + final AtomicReference pcontext = new AtomicReference<>(null); + + exec.addMethodContextVisitor(pcontext::set); + exec.run(); + + final MethodContext methodContext = pcontext.get(); + for (InstructionContext ic : methodContext.getInstructionContexts()) + { + final Instruction instr = ic.getInstruction(); + if (!(instr instanceof InvokeStatic)) + { + continue; + } + + if (fillRectPool.equals(((InvokeStatic) instr).getMethod())) + { + List pops = ic.getPops(); + + // Last pop is constant value 0, before that are vars in order + assert pops.size() == 5 : "If this fails cause of this add in 1 for obfuscation, I don't think that happens here though"; + + int i = 0; + Instruction pushed = pops.get(i++).getPushed().getInstruction(); + assert (pushed instanceof PushConstantInstruction) && ((PushConstantInstruction) pushed).getConstant().equals(0); + + for (int varI = 3; i < 5; i++, varI--) + { + pushed = pops.get(i).getPushed().getInstruction(); + assert (pushed instanceof ILoad) && ((ILoad) pushed).getVariableIndex() == varI; + } + + Instructions ins = instr.getInstructions(); + ins.replace(instr, new InvokeStatic(ins, CLEARBUFFER)); + log.debug("[DEBUG] Injected drawRectangle at {}", methodContext.getMethod().getPoolMethod()); + } + } + } +} diff --git a/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java new file mode 100644 index 0000000000..218b618320 --- /dev/null +++ b/openosrs-injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2019, Lucas + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this source tree. + * + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors.raw; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.InjectUtil; +import com.openosrs.injector.injection.InjectData; +import com.openosrs.injector.injectors.AbstractInjector; +import java.util.HashSet; +import java.util.ListIterator; +import java.util.Set; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.Label; +import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction; +import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction; +import net.runelite.asm.attributes.code.instructions.GetStatic; +import net.runelite.asm.attributes.code.instructions.IMul; +import net.runelite.asm.attributes.code.instructions.InvokeStatic; +import net.runelite.asm.signature.Signature; +import static com.openosrs.injector.injection.InjectData.HOOKS; + +public class DrawAfterWidgets extends AbstractInjector +{ + public DrawAfterWidgets(InjectData inject) + { + super(inject); + } + + public void inject() + { + /* + * This call has to be injected using raw injection because the + * drawWidgets method gets inlined in some revisions. If it wouldn't be, + * mixins would be used to add the call to the end of drawWidgets. + + * --> This hook depends on the positions of "if (535573958 * kl != -1)" and "jz.db();". + + + * Revision 180 - client.gs(): + * ______________________________________________________ + + * @Export("drawLoggedIn") + * final void drawLoggedIn() { + * if(rootInterface != -1) { + * ClientPreferences.method1809(rootInterface); + * } + + * int var1; + * for(var1 = 0; var1 < rootWidgetCount; ++var1) { + * if(__client_od[var1]) { + * __client_ot[var1] = true; + * } + + * __client_oq[var1] = __client_od[var1]; + * __client_od[var1] = false; + * } + + * __client_oo = cycle; + * __client_lq = -1; + * __client_ln = -1; + * UserComparator6.__fg_jh = null; + * if(rootInterface != -1) { + * rootWidgetCount = 0; + * Interpreter.drawWidgets(rootInterface, 0, 0, SoundCache.canvasWidth, Huffman.canvasHeight, 0, 0, -1); + * } + + * <-- here + + * Rasterizer2D.Rasterizer2D_resetClip(); + * ______________________________________________________ + */ + + boolean injected = false; + + Method noClip = InjectUtil.findMethod(inject, "Rasterizer2D_resetClip", "Rasterizer2D", null); // !!!!! + + if (noClip == null) + { + throw new InjectException("Mapped method \"Rasterizer2D_resetClip\" could not be found."); + } + + net.runelite.asm.pool.Method poolNoClip = noClip.getPoolMethod(); + + for (ClassFile c : inject.getVanilla()) + { + for (Method m : c.getMethods()) + { + if (m.getCode() == null) + { + continue; + } + + Instructions instructions = m.getCode().getInstructions(); + + Set