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