diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..b8d61aa --- /dev/null +++ b/404.html @@ -0,0 +1,830 @@ + + + + + + + + + + + + + + + + + + Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Images/Contribute/ForkTheRepo.png b/Reloaded/Images/Contribute/ForkTheRepo.png new file mode 100644 index 0000000..db1f529 Binary files /dev/null and b/Reloaded/Images/Contribute/ForkTheRepo.png differ diff --git a/Reloaded/Images/Contribute/GitHubDesktop.png b/Reloaded/Images/Contribute/GitHubDesktop.png new file mode 100644 index 0000000..75b8f71 Binary files /dev/null and b/Reloaded/Images/Contribute/GitHubDesktop.png differ diff --git a/Reloaded/Images/Contribute/LocalRun.png b/Reloaded/Images/Contribute/LocalRun.png new file mode 100644 index 0000000..d15f556 Binary files /dev/null and b/Reloaded/Images/Contribute/LocalRun.png differ diff --git a/Reloaded/Images/Contribute/OpenPR.png b/Reloaded/Images/Contribute/OpenPR.png new file mode 100644 index 0000000..e2d1aba Binary files /dev/null and b/Reloaded/Images/Contribute/OpenPR.png differ diff --git a/Reloaded/Images/Contribute/OpenPullRequest.png b/Reloaded/Images/Contribute/OpenPullRequest.png new file mode 100644 index 0000000..e2d1aba Binary files /dev/null and b/Reloaded/Images/Contribute/OpenPullRequest.png differ diff --git a/Reloaded/Images/Contribute/Rider.png b/Reloaded/Images/Contribute/Rider.png new file mode 100644 index 0000000..27f380c Binary files /dev/null and b/Reloaded/Images/Contribute/Rider.png differ diff --git a/Reloaded/Images/Nexus-Heart-40.png b/Reloaded/Images/Nexus-Heart-40.png new file mode 100644 index 0000000..3337565 Binary files /dev/null and b/Reloaded/Images/Nexus-Heart-40.png differ diff --git a/Reloaded/Images/Nexus-Heart.png b/Reloaded/Images/Nexus-Heart.png new file mode 100644 index 0000000..43f7a26 Binary files /dev/null and b/Reloaded/Images/Nexus-Heart.png differ diff --git a/Reloaded/Images/Nexus-Heart.psd b/Reloaded/Images/Nexus-Heart.psd new file mode 100644 index 0000000..473c865 Binary files /dev/null and b/Reloaded/Images/Nexus-Heart.psd differ diff --git a/Reloaded/Images/Nexus-Icon-40.png b/Reloaded/Images/Nexus-Icon-40.png new file mode 100644 index 0000000..8781822 Binary files /dev/null and b/Reloaded/Images/Nexus-Icon-40.png differ diff --git a/Reloaded/Images/Nexus-Icon.png b/Reloaded/Images/Nexus-Icon.png new file mode 100644 index 0000000..3e611b4 Binary files /dev/null and b/Reloaded/Images/Nexus-Icon.png differ diff --git a/Reloaded/Images/Reloaded-Heart-40.png b/Reloaded/Images/Reloaded-Heart-40.png new file mode 100644 index 0000000..6fdf9da Binary files /dev/null and b/Reloaded/Images/Reloaded-Heart-40.png differ diff --git a/Reloaded/Images/Reloaded-Heart.png b/Reloaded/Images/Reloaded-Heart.png new file mode 100644 index 0000000..0ed75b3 Binary files /dev/null and b/Reloaded/Images/Reloaded-Heart.png differ diff --git a/Reloaded/Images/Reloaded-Icon-40.png b/Reloaded/Images/Reloaded-Icon-40.png new file mode 100644 index 0000000..9a43d61 Binary files /dev/null and b/Reloaded/Images/Reloaded-Icon-40.png differ diff --git a/Reloaded/Images/Reloaded-Icon.png b/Reloaded/Images/Reloaded-Icon.png new file mode 100644 index 0000000..ae0d6d2 Binary files /dev/null and b/Reloaded/Images/Reloaded-Icon.png differ diff --git a/Reloaded/Images/Reloaded-Templates.7z b/Reloaded/Images/Reloaded-Templates.7z new file mode 100644 index 0000000..371a063 Binary files /dev/null and b/Reloaded/Images/Reloaded-Templates.7z differ diff --git a/Reloaded/LICENSE b/Reloaded/LICENSE new file mode 100644 index 0000000..7de5f64 --- /dev/null +++ b/Reloaded/LICENSE @@ -0,0 +1,779 @@ +# The Reloaded Project License + +Components of the Reloaded-Project are governed by the *GPLv3* license as of May 2023. +Prior versions of the project were under the LGPLv3 license. + +The complete license text can be found at the end of this document. + +This FAQ is meant to clarify our licensing choice and its implications. +Please note, though, that the full license text is the final legal authority. + +## Why was GPL v3 chosen? + +The primary objective is to prevent closed-source, commercial exploitation of the project. +We want to ensure that the project isn't used within a proprietary environment for +profit-making purposes such as: + +- Being sold behind a Patreon paywall. +- Being integrated into a closed-source commercial product for sale. + +The Reloaded Project is a labour of love from unpaid hobbyist volunteers. +It wasn't designed for commercial use, and exploiting it for profit feels fundamentally unfair. + +While the GPLv3 license doesn't prohibit commercial use outright, it does prevent commercial +exploitation by requiring that contributions are given back to the open-source community. + +In that fashion, everyone can benefit from the projects under the Reloaded label. + +## Can I use Reloaded Libraries Commercially? + +You can as long as the resulting produce is also licensed under GPLv3, and thus open source. + +## Can I use Reloaded Libraries in a closed-source application? + +The license terms do not permit this. + +However, if your software is completely non-commercial, meaning it's neither +sold for profit, funded in development, nor hidden behind a paywall (like Patreon), +we will just look the other way. + +This often applies to non-professional programmers, learners, or those +with no intent to exploit the project. We believe in understanding and +leniency for those who might not know better. + +GPL v3 exists to protect the project and its contributors. +If you're not exploiting the project for commercial gain, you're not hurting us; +and we will not enforce the terms of the GPL. + +If you are interested in obtaining a commercial license, or want an explicit written exemption, +please get in touch with the repository owners. + +## Can I link Reloaded Libraries statically/dynamically? + +Yes, as long as you adhere to the GPLv3 license terms, you're permitted to statically +link Reloaded Libraries into your project, for instance, through the use of NativeAOT or ILMerge. + +## Guidelines for Non-Commercial Use + +We support and encourage the non-commercial use of Reloaded Libraries. +Non-commercial use generally refers to the usage of our libraries for personal projects, +educational purposes, academic research, or use by non-profit organizations. + +**Personal Projects:** You're free to use our libraries for projects that you undertake +for your own learning, hobby or personal enjoyment. This includes creating mods for your +favorite games or building your own applications for personal use. + +**Educational Use:** Teachers and students are welcome to use our libraries as a learning +resource. You can incorporate them into your teaching materials, student projects, coding +bootcamps, workshops, etc. + +**Academic Research:** Researchers may use our libraries for academic and scholarly research. +We'd appreciate if you cite our work in any publications that result from research involving our libraries. + +**Non-profit Organizations:** If you're part of a registered non-profit organization, +you can use our libraries in your projects. However, any derivative work that uses our +libraries must also be released under the GPL. + +Please remember, if your usage of our libraries evolves from non-commercial to commercial, +you must ensure compliance with the terms of the GPL v3 license. + +## Attribution Requirements + +As Reloaded Project is a labor of love, done purely out of passion and with an aim to contribute +to the broader community, we highly appreciate your support in providing attribution when using +our libraries. While not legally mandatory under the GPL v3, it is a simple act that can go a long +way in recognizing the efforts of our contributors and fostering an open and collaborative atmosphere. + +If you choose to provide attribution (and we hope you do!), here are some guidelines: + +- **Acknowledge the Use of Reloaded Libraries:** Mention that your project uses or is based on Reloaded libraries. + This could be in your project's readme, a credits page on a website, a manual, or within the software itself. + +- **Link to the Project:** If possible, provide a link back to the Reloaded Project. + This allows others to explore and potentially benefit from our work. + +Remember, attribution is more than just giving credit,,, it's a way of saying thank you 👉👈, fostering reciprocal +respect, and acknowledging the power of collaborative open-source development. + +We appreciate your support and look forward to seeing what amazing projects you create using Reloaded libraries! + +## Code from MIT/BSD Licensed Projects + +In some rare instances, code from more permissively licensed projects, such as those under the +`MIT` or `BSD` licenses, may be referenced, incorporated, or slightly modified within the Reloaded Project. + +It's important to us to respect the terms and intentions of these permissive licenses, +which often allow their code to be used in a wide variety of contexts, including in GPL-licensed projects like ours. + +In these cases, the Reloaded Project is committed to clearly disclosing the usage of such code: + +- **Method-Level Disclosure:** For individual methods or small code snippets, we use appropriate + attribution methods, like programming language attributes. For example, methods borrowed or adapted + from MIT-licensed projects might be marked with a `[MITLicense]` attribute. + +- **File-Level Disclosure:** For larger amounts of code, such as entire files or modules, we'll include + the original license text at the top of the file and clearly indicate which portions of the code originate + from a differently-licensed project. + +- **Project-Level Disclosure:** If an entire library or significant portion of a project under a more permissive + license is used, we will include an acknowledgment in a prominent location, such as the readme file or the + project's license documentation. + +This approach ensures we honor the contributions of the open source community at large, respect the original +licenses, and maintain transparency with our users about where code originates from. + +Any files/methods or snippets marked with those attributes may be consumed using their original license terms. +i.e. If a method is marked with `[MITLicense]`, you may use it under the terms of the MIT license. + +## Contributing to the Reloaded Project + +We welcome and appreciate contributions to the Reloaded Project! +By contributing, you agree to share your changes under the same GPLv3 license, +helping to make the project better for everyone. + +------------------------ +# Reloaded.Memory + + 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 + +------------------------ +# .NET Community Toolkit + +Copyright © .NET Foundation and Contributors + +All rights reserved. + +## MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the “Software”), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of +the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Reloaded/Pages/contributing/index.html b/Reloaded/Pages/contributing/index.html new file mode 100644 index 0000000..4579eec --- /dev/null +++ b/Reloaded/Pages/contributing/index.html @@ -0,0 +1,1025 @@ + + + + + + + + + + + + + + + + + + + + + + How to Document - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Contributing to the Wiki: Locally

+
+

Info

+

This page shows you how to contribute to any documentation page or wiki +based on this template.

+
+
+

Note

+

This theme is forked from my theme for Nexus Docs; +and this page is synced with that.

+
+

Tutorial

+
+

Note

+

If you are editing the repository with the theme itself on Windows, it might be a good idea to run +git config core.symlinks true first to allow git to create symlinks on clone.

+
+

You should learn the basics of git, an easy way is to give GitHub Desktop (Tutorial) a go.
+It's only 15 minutes 😀.

+
    +
  1. Create a GitHub account.
  2. +
  3. +

    Fork this repository:

    +

    Image

    +

    This will create a copy of the repository on your own user account, which you will be able to edit.

    +
  4. +
  5. +

    Clone this repository.

    +

    For example, using GitHub Desktop: +Image

    +
  6. +
  7. +

    Make changes inside the docs folder.

    +

    Image

    +

    Consider using a Markdown Cheat Sheet if you are new to markdown.

    +

    I recommend using a markdown editor such as Typora.
    +Personally I just work from inside Rider.

    +
  8. +
  9. +

    Commit the changes and push to GitHub.

    +
  10. +
  11. +

    Open a Pull Request.

    +

    Image

    +

    Opening a Pull Request will allow us to review your changes before adding them with the main official page. If everything's good, we'll hit the merge button and add your changes to the official repository.

    +
  12. +
+

Website Live Preview

+

If you are working on the wiki locally, you can generate a live preview the full website. +Here's a quick guide of how you could do it from your command prompt (cmd).

+
    +
  1. +

    Install Python 3

    +

    If you have winget installed, or Windows 11, you can do this from the command prompt.

    +
    +
    +
    +
    winget install Python.Python.3
    +
    +
    +
    +
    pacman -S python-pip # you should already have Python
    +
    +
    +
    +
    +

    Otherwise download Python 3 from the official website or package manager.

    +
  2. +
  3. +

    Install Material for MkDocs and Plugins (Python package)

    +
    +
    +
    +
    # Restart your command prompt before running this command.
    +pip install mkdocs-material
    +pip install mkdocs-redirects
    +
    +
    +
    +

    On Linux, there is a chance that python might be a core part of your OS, meaning +that you ideally shouldn't touch the system installation.

    +

    Use virtual environments instead.

    +
    python -m venv mkdocs # Create the environment
    +source ~/mkdocs/bin/activate # Enter the environment
    +
    +pip install mkdocs-material
    +pip install mkdocs-redirects
    +
    +

    Make sure you enter the environment before any time you run mkdocs.

    +
    +
    +
    +
  4. +
  5. +

    Open a command prompt in the folder containing mkdocs.yml. and run the site locally. +

    # Move to project folder.
    +cd <Replace this with full path to folder containing `mkdocs.yml`>
    +mkdocs serve
    +

    +

    Image

    +

    Copy the address to your web browser and enjoy the live preview; any changes you save will be shown instantly.

    +
  6. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Pages/index.html b/Reloaded/Pages/index.html new file mode 100644 index 0000000..5e8c9da --- /dev/null +++ b/Reloaded/Pages/index.html @@ -0,0 +1,1021 @@ + + + + + + + + + + + + + + + + + + + + Index - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + + +

Index

+ +
+

The Reloaded MkDocs Theme

+ +

+ A Theme for MkDocs Material. +
+ That resembles the look of Reloaded. +
+ +

About

+

This it the NexusMods theme for Material-MkDocs, inspired by the look of Reloaded-II.

+

The overall wiki theme should look fairly close to the actual launcher appearance.

+

Setup From Scratch

+
    +
  • Add this repository as submodule to docs/Reloaded.
  • +
  • Save the following configuration as mkdocs.yml in your repository root.
  • +
+
site_name: Reloaded MkDocs Theme
+site_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+
+repo_name: Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+repo_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+
+extra:
+  social:
+    - icon: fontawesome/brands/github
+      link: https://github.com/Reloaded-Project
+    - icon: fontawesome/brands/twitter
+      link: https://twitter.com/thesewer56?lang=en-GB
+
+extra_css:
+  - Reloaded/Stylesheets/extra.css
+
+markdown_extensions:
+  - admonition
+  - tables
+  - pymdownx.details
+  - pymdownx.highlight
+  - pymdownx.superfences:
+      custom_fences:
+        - name: mermaid
+          class: mermaid
+          format: !!python/name:pymdownx.superfences.fence_code_format
+  - pymdownx.tasklist
+  - def_list
+  - meta
+  - md_in_html
+  - attr_list
+  - footnotes
+  - pymdownx.tabbed:
+      alternate_style: true
+  - pymdownx.emoji:
+      emoji_index: !!python/name:materialx.emoji.twemoji
+      emoji_generator: !!python/name:materialx.emoji.to_svg
+
+theme:
+  name: material
+  palette:
+    scheme: reloaded-slate
+  features:
+    - navigation.instant
+
+plugins:
+  - search
+
+nav:
+  - Home: index.md
+
+
    +
  • Add a GitHub Actions workload in .github/workflows/DeployMkDocs.yml.
  • +
+
name: DeployMkDocs
+
+# Controls when the action will run. 
+on:
+  # Triggers the workflow on push on the master branch
+  push:
+    branches: [ main ]
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  build:
+    # The type of runner that the job will run on
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - name: Checkout Branch
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+
+      # Deploy MkDocs
+      - name: Deploy MkDocs
+        # You may pin to the exact commit or the version.
+        # uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb
+        uses: mhausenblas/mkdocs-deploy-gh-pages@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          REQUIREMENTS: ./docs/requirements.txt
+
+
    +
  • Push to GitHub, this should produce a GitHub Pages site.
  • +
  • Go to Settings -> Pages in your repo and select gh-pages branch to enable GitHub pages.
  • +
+

Your page should then be live.

+
+

Tip

+

Refer to Contributing for instructions on how to locally edit and modify the wiki.

+
+
+

Note

+

For Reloaded3 theme use reloaded3-slate instead of reloaded-slate.

+
+

Extra

+
+

Info

+

Most documentation pages will also include additional plugins; some which are used in the pages here.
+Here is a sample complete mkdocs.yml you can copy to your project for reference.

+
+

Technical Questions

+

If you have questions/bug reports/etc. feel free to Open an Issue.

+

Happy Documenting ❤️

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Pages/license/index.html b/Reloaded/Pages/license/index.html new file mode 100644 index 0000000..4b7e507 --- /dev/null +++ b/Reloaded/Pages/license/index.html @@ -0,0 +1,1193 @@ + + + + + + + + + + + + + + + + + + + + + + + + License - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

The Reloaded Project License

+
+

Most components of the Reloaded are governed by the GPLv3 license.

+
+
+

In some, albeit rare scenarios, certain libraries might be licensed under LGPLv3 instead.

+
+

This is a FAQ meant to clarify the licensing choice and its implications. +Please note, though, that the full license text is the final legal authority.

+

Why was GPL v3 chosen?

+
+

The primary objective is to prevent closed-source, commercial exploitation of the project.

+
+

We want to ensure that the project isn't used within a proprietary environment for +profit-making purposes such as:

+
    +
  • Being sold behind a Patreon paywall.
  • +
  • Being integrated into a closed-source commercial product for sale.
  • +
+
+

The Reloaded Project is a labour of love from unpaid hobbyist volunteers.

+
+

Exploiting that work for profit feels fundamentally unfair.

+

While the GPLv3 license doesn't prohibit commercial use outright, it does prevent commercial +exploitation by requiring that contributions are given back to the open-source community.

+

In that fashion, everyone can benefit from the projects under the Reloaded label.

+

Can I use Reloaded Libraries Commercially?

+

You can as long as the resulting produce is also licensed under GPLv3, and thus open source.

+

Can I use Reloaded Libraries in a closed-source application?

+
+

The license terms do not permit this.

+
+

However, if your software is completely non-commercial, meaning it's neither +sold for profit, funded in development, nor hidden behind a paywall (like Patreon), +we probably just look the other way.

+

This often applies to non-professional programmers, learners, or those +with no intent to exploit the project. We believe in understanding and +leniency for those who might not know better.

+

GPL v3 exists to protect the project and its contributors. If you're not exploiting the project for commercial +gain, you're not hurting us; and we will not enforce the terms of the GPL.

+

If you are interested in obtaining a commercial license, or want an explicit written exemption, +please get in touch with the repository owners.

+ +

Yes, as long as you adhere to the GPLv3 license terms, you're permitted to statically +link Reloaded Libraries into your project, for instance, through the use of NativeAOT or ILMerge.

+

Guidelines for Non-Commercial Use

+

We support and encourage the non-commercial use of Reloaded Libraries. +Non-commercial use generally refers to the usage of our libraries for personal projects, +educational purposes, academic research, or use by non-profit organizations.

+

Personal Projects

+

You're free to use our libraries for projects that you undertake +for your own learning, hobby or personal enjoyment. This includes creating mods for your +favorite games or building your own applications for personal use.

+

Educational Use

+

Teachers and students are welcome to use our libraries as a learning +resource. You can incorporate them into your teaching materials, student projects, coding +bootcamps, workshops, etc.

+

Academic Research

+

Researchers may use our libraries for academic and scholarly research. +We'd appreciate if you cite our work in any publications that result from research involving our libraries.

+

Non-profit Organizations

+

If you're part of a registered non-profit organization, +you can use our libraries in your projects. However, any derivative work that uses our +libraries must also be released under the GPL.

+

Please remember, if your usage of our libraries evolves from non-commercial to commercial, +you must ensure compliance with the terms of the GPL v3 license.

+

Attribution Requirements

+

As Reloaded Project is a labor of love, done purely out of passion and with an aim to contribute +to the broader community, we highly appreciate your support in providing attribution when using +our libraries.

+

While not legally mandatory under GPL v3, it is a simple act that can go a long +way in recognizing the efforts of our contributors and fostering an open and collaborative atmosphere.

+

If you choose to provide attribution (and we hope you do!), here are some guidelines:

+
    +
  • +

    Acknowledge the Use of Reloaded Libraries: Mention that your project uses or is based on Reloaded libraries. + This could be in your project's readme, a credits page on a website, a manual, or within the software itself.

    +
  • +
  • +

    Link to the Project: If possible, provide a link back to the Reloaded Project. + This allows others to explore and potentially benefit from our work.

    +
  • +
+

Remember, attribution is more than just giving credit,,, it's a way of saying thank you 👉👈, fostering reciprocal +respect, and acknowledging the power of collaborative open-source development.

+

We appreciate your support and look forward to seeing what amazing projects you create using Reloaded libraries!

+

Code from MIT/BSD Licensed Projects

+

In some rare instances, code from more permissively licensed projects, such as those under the +MIT or BSD licenses, may be referenced, incorporated, or slightly modified within the Reloaded Project.

+

It's important to us to respect the terms and intentions of these permissive licenses, +which often allow their code to be used in a wide variety of contexts, including in GPL-licensed projects like ours.

+

In these cases, the Reloaded Project is committed to clearly disclosing the usage of such code:

+
    +
  • +

    Method-Level Disclosure: For individual methods or small code snippets, we use appropriate + attribution methods, like programming language attributes. For example, methods borrowed or adapted + from MIT-licensed projects might be marked with a [MITLicense] attribute.

    +
  • +
  • +

    File-Level Disclosure: For larger amounts of code, such as entire files or modules, we'll include + the original license text at the top of the file and clearly indicate which portions of the code originate + from a differently-licensed project.

    +
  • +
  • +

    Project-Level Disclosure: If an entire library or significant portion of a project under a more permissive + license is used, we will include an acknowledgment in a prominent location, such as the readme file or the + project's license documentation.

    +
  • +
+

This approach ensures we honor the contributions of the open source community at large, respect the original +licenses, and maintain transparency with our users about where code originates from.

+

Any files/methods or snippets marked with those attributes may be consumed using their original license terms.

+

i.e. If a method is marked with [MITLicense], you may use it under the terms of the MIT license.

+

Contributing to the Reloaded Project

+

We welcome and appreciate contributions to the Reloaded Project! +By contributing, you agree to share your changes under the same GPLv3 license, +helping to make the project better for everyone.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Pages/testing-zone/index.html b/Reloaded/Pages/testing-zone/index.html new file mode 100644 index 0000000..e81713b --- /dev/null +++ b/Reloaded/Pages/testing-zone/index.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + Testing Zone - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Testing Zone

+
+

Info

+

This is a dummy page with various Material MkDocs controls and features scattered throughout for testing.

+
+

Custom Admonitions

+
+

Reloaded Admonition

+

An admonition featuring a Reloaded logo. +My source is in Stylesheets/extra.css as Custom 'reloaded' admonition.

+
+
+

Heart Admonition

+

An admonition featuring a heart; because we want to contribute back to the open source community.
+My source is in Stylesheets/extra.css as Custom 'reloaded heart' admonition.

+
+
+

Nexus Admonition

+

An admonition featuring a Nexus logo. +My source is in Stylesheets/extra.css as Custom 'nexus' admonition.

+
+
+

Heart Admonition

+

An admonition featuring a heart; because we want to contribute back to the open source community.
+My source is in Stylesheets/extra.css as Custom 'nexus heart' admonition.

+
+

Mermaid Diagram

+

Flowchart (Source: Nexus Archive Library):

+
flowchart TD
+    subgraph Block 2
+        BigFile1.bin
+    end
+
+    subgraph Block 1
+        BigFile0.bin
+    end
+
+    subgraph Block 0
+        ModConfig.json -.-> Updates.json 
+        Updates.json -.-> more["... more .json files"]        
+    end
+

Sequence Diagram (Source: Reloaded3 Specification):

+
sequenceDiagram
+
+    % Define Items
+    participant Mod Loader
+    participant Virtual FileSystem (VFS)
+    participant CRI CPK Archive Support
+    participant Persona 5 Royal Support
+    participant Joker Costume
+
+    % Define Actions
+    Mod Loader->>Persona 5 Royal Support: Load Mod
+    Persona 5 Royal Support->>Mod Loader: Request CRI CPK Archive Support API
+    Mod Loader->>Persona 5 Royal Support: Receive CRI CPK Archive Support Instance
+
+    Mod Loader->>Joker Costume: Load Mod
+    Mod Loader-->Persona 5 Royal Support: Notification: 'Loaded Joker Costume'
+    Persona 5 Royal Support->>CRI CPK Archive Support: Add Files from 'Joker Costume' to CPK Archive (via API)
+

State Diagram (Source: Mermaid Docs):

+
stateDiagram-v2
+    [*] --> Still
+    Still --> [*]
+
+    Still --> Moving
+    Moving --> Still
+    Moving --> Crash
+    Crash --> [*]
+

Class Diagram (Arbitrary)

+
classDiagram
+    class Animal
+    `NexusMobile™` <|-- Car
+
+

Note

+

At time of writing, version of Mermaid is a bit outdated here; and other diagrams might not render correctly +(even on unmodified theme); thus certain diagrams have been omitted from here.

+
+

Code Block

+

Snippet from C# version of Sewer's Virtual FileSystem (VFS):

+
/// <summary>
+/// Tries to get files for a specific folder, assuming the input path is already in upper case.
+/// </summary>
+/// <param name="folderPath">The folder to find. Already lowercase.</param>
+/// <param name="value">The returned folder instance.</param>
+/// <returns>True if found, else false.</returns>
+[MethodImpl(MethodImplOptions.AggressiveInlining)]
+public bool TryGetFolderUpper(ReadOnlySpan<char> folderPath, out SpanOfCharDict<TTarget> value)
+{
+    // Must be O(1)
+    value = default!;        
+
+    // Compare equality.
+    // Note to devs: Do not invert branches, we optimise for hot paths here.
+    if (folderPath.StartsWith(Prefix))
+    {
+        // Check for subfolder in branchless way.
+        // In CLR, bool is length 1, so conversion to byte should be safe.
+        // Even suppose it is not; as long as code is little endian; truncating int/4 bytes to byte still results 
+        // in correct answer.
+        var hasSubfolder = Prefix.Length != folderPath.Length;
+        var hasSubfolderByte = Unsafe.As<bool, byte>(ref hasSubfolder);
+        var nextFolder = folderPath.SliceFast(Prefix.Length + hasSubfolderByte);
+
+        return SubfolderToFiles.TryGetValue(nextFolder, out value!);
+    }
+
+    return false;
+}
+
+

Something more number heavy, Fast Inverse Square Root from Quake III Arena (unmodified). +

float Q_rsqrt( float number )
+{
+    long i;
+    float x2, y;
+    const float threehalfs = 1.5F;
+
+    x2 = number * 0.5F;
+    y  = number;
+    i  = * ( long * ) &y;                       // evil floating point bit level hacking
+    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
+    y  = * ( float * ) &i;
+    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
+//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
+
+    return y;
+}
+

+

Default Admonitions

+
+

Note

+

Test

+
+
+

Abstract

+

Test

+
+
+

Info

+

Test

+
+
+

Tip

+

Test

+
+
+

Success

+

Test

+
+
+

Question

+

Test

+
+
+

Warning

+

Test

+
+
+

Failure

+

Test

+
+
+

Danger

+

Test

+
+
+

Bug

+

Test

+
+
+

Example

+

Test

+
+
+

Quote

+

Test

+
+

Tables

+ + + + + + + + + + + + + + + + + + + + + +
MethodDescription
GET Fetch resource
PUT Update resource
DELETE Delete resource
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Readme/index.html b/Reloaded/Readme/index.html new file mode 100644 index 0000000..78c5d49 --- /dev/null +++ b/Reloaded/Readme/index.html @@ -0,0 +1,844 @@ + + + + + + + + + + + + + + + + + + + + Readme - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/Stylesheets/Montserrat-Bold.ttf b/Reloaded/Stylesheets/Montserrat-Bold.ttf new file mode 100644 index 0000000..efddc83 Binary files /dev/null and b/Reloaded/Stylesheets/Montserrat-Bold.ttf differ diff --git a/Reloaded/Stylesheets/Montserrat-Regular.ttf b/Reloaded/Stylesheets/Montserrat-Regular.ttf new file mode 100644 index 0000000..aa9033a Binary files /dev/null and b/Reloaded/Stylesheets/Montserrat-Regular.ttf differ diff --git a/Reloaded/Stylesheets/Montserrat-SemiBold.ttf b/Reloaded/Stylesheets/Montserrat-SemiBold.ttf new file mode 100644 index 0000000..cbf44db Binary files /dev/null and b/Reloaded/Stylesheets/Montserrat-SemiBold.ttf differ diff --git a/Reloaded/Stylesheets/extra.css b/Reloaded/Stylesheets/extra.css new file mode 100644 index 0000000..b183a12 --- /dev/null +++ b/Reloaded/Stylesheets/extra.css @@ -0,0 +1,597 @@ +/* + Nexus Mods Theme +*/ + +:root { + --md-admonition-icon--nexus: url('../Images/Nexus-Icon-40.png'); + --md-admonition-icon--nexusheart: url('../Images/Nexus-Heart-40.png'); + --md-admonition-icon--reloaded: url('../Images/Reloaded-Icon-40.png'); + --md-admonition-icon--reloadedheart: url('../Images/Reloaded-Heart-40.png'); +} + +/* Slate theme, i.e. dark mode */ + +[data-md-color-scheme="nexus-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 217, 143, 64; + + /* Primary color shades */ + --md-primary-fg-color: #D98F40; + --md-primary-fg-color--light: #E0A362; + --md-primary-fg-color--dark: #C87B28; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #C87B28; + --md-accent-fg-color--transparent: #C87B2810; + + --md-accent-bg-color: #2A2C2B; + --md-accent-bg-color--light: #2A2C2B; + +/* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. +*/ + --md-hue: 31; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.12); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="nexus-slate"] .md-header { + background-color: var(--nexus-background-primary); +} + +[data-md-color-scheme="reloaded-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 182, 99, 99; + + /* Primary color shades */ + --md-primary-fg-color: #793939; + --md-primary-fg-color--light: #B66363; + --md-primary-fg-color--lightest: #d6a8a8; + --md-primary-fg-color--dark: #572929; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #793939; + --md-accent-fg-color--transparent: #79393960; + + --md-accent-bg-color: #181818; + --md-accent-bg-color--light: #181818; + + /* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. + */ + --md-hue: 0; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.04); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color--lightest); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="reloaded-slate"] .md-typeset a:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav--primary .md-nav__item--active>.md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + + +[data-md-color-scheme="reloaded3-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 63, 153, 217; + + /* Primary color shades */ + --md-primary-fg-color: #fa7774; + --md-primary-fg-color--light: #fc918b; + --md-primary-fg-color--dark: #e96060; + --md-primary-fg-color--darker: #d74f52; + --md-primary-fg-color--darkest: #c53e44; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #e96060; + --md-accent-fg-color--transparent: #e9606030; + + --md-accent-bg-color: #2A2C2B; + --md-accent-bg-color--light: #2A2C2B; + + /* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. + */ + --md-hue: 1; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.12); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-header { + background-color: var(--md-primary-fg-color--darker); +} + +@media screen and (max-width: 76.1875em) { + /* Title in Drawer */ + [data-md-color-scheme="reloaded3-slate"] .md-nav--primary .md-nav__title[for=__drawer] { + background-color: var(--md-primary-fg-color--darker); + color: var(--md-primary-bg-color); + font-weight: 700; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-nav__source { + background-color: var(--md-primary-fg-color--darkest); + color: var(--md-primary-bg-color); +} + +.md-nav__title { + color: var(--md-default-fg-color); +} + +.md-nav__link { + color: var(--md-default-fg-color--light); +} + +/* Custom 'nexus' admonition */ +.md-typeset .admonition.nexus, +.md-typeset details.nexus { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexus > .admonition-title, +.md-typeset .nexus > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexus > .admonition-title::before, +.md-typeset .nexus > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexus); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.nexusheart, +.md-typeset details.nexusheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexusheart > .admonition-title, +.md-typeset .nexusheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexusheart > .admonition-title::before, +.md-typeset .nexusheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexusheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'reloaded' admonition */ + +.md-typeset .admonition.reloaded, +.md-typeset details.reloaded { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloaded > .admonition-title, +.md-typeset .reloaded > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloaded > .admonition-title::before, +.md-typeset .reloaded > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloaded); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.reloadedheart, +.md-typeset details.reloadedheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloadedheart > .admonition-title, +.md-typeset .reloadedheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloadedheart > .admonition-title::before, +.md-typeset .reloadedheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloadedheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Add Title Font */ +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-Regular.ttf") format("truetype"); + font-weight: normal; +} + +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-SemiBold.ttf") format("truetype"); + font-weight: 600; +} + +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-Bold.ttf") format("truetype"); + font-weight: bold; +} + +/* Headers */ +[data-md-color-scheme="nexus-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} \ No newline at end of file diff --git a/Reloaded/docs/Images/Contribute/ForkTheRepo.png b/Reloaded/docs/Images/Contribute/ForkTheRepo.png new file mode 100644 index 0000000..db1f529 Binary files /dev/null and b/Reloaded/docs/Images/Contribute/ForkTheRepo.png differ diff --git a/Reloaded/docs/Images/Contribute/GitHubDesktop.png b/Reloaded/docs/Images/Contribute/GitHubDesktop.png new file mode 100644 index 0000000..75b8f71 Binary files /dev/null and b/Reloaded/docs/Images/Contribute/GitHubDesktop.png differ diff --git a/Reloaded/docs/Images/Contribute/LocalRun.png b/Reloaded/docs/Images/Contribute/LocalRun.png new file mode 100644 index 0000000..d15f556 Binary files /dev/null and b/Reloaded/docs/Images/Contribute/LocalRun.png differ diff --git a/Reloaded/docs/Images/Contribute/OpenPR.png b/Reloaded/docs/Images/Contribute/OpenPR.png new file mode 100644 index 0000000..e2d1aba Binary files /dev/null and b/Reloaded/docs/Images/Contribute/OpenPR.png differ diff --git a/Reloaded/docs/Images/Contribute/OpenPullRequest.png b/Reloaded/docs/Images/Contribute/OpenPullRequest.png new file mode 100644 index 0000000..e2d1aba Binary files /dev/null and b/Reloaded/docs/Images/Contribute/OpenPullRequest.png differ diff --git a/Reloaded/docs/Images/Contribute/Rider.png b/Reloaded/docs/Images/Contribute/Rider.png new file mode 100644 index 0000000..27f380c Binary files /dev/null and b/Reloaded/docs/Images/Contribute/Rider.png differ diff --git a/Reloaded/docs/Images/Nexus-Heart-40.png b/Reloaded/docs/Images/Nexus-Heart-40.png new file mode 100644 index 0000000..3337565 Binary files /dev/null and b/Reloaded/docs/Images/Nexus-Heart-40.png differ diff --git a/Reloaded/docs/Images/Nexus-Heart.png b/Reloaded/docs/Images/Nexus-Heart.png new file mode 100644 index 0000000..43f7a26 Binary files /dev/null and b/Reloaded/docs/Images/Nexus-Heart.png differ diff --git a/Reloaded/docs/Images/Nexus-Heart.psd b/Reloaded/docs/Images/Nexus-Heart.psd new file mode 100644 index 0000000..473c865 Binary files /dev/null and b/Reloaded/docs/Images/Nexus-Heart.psd differ diff --git a/Reloaded/docs/Images/Nexus-Icon-40.png b/Reloaded/docs/Images/Nexus-Icon-40.png new file mode 100644 index 0000000..8781822 Binary files /dev/null and b/Reloaded/docs/Images/Nexus-Icon-40.png differ diff --git a/Reloaded/docs/Images/Nexus-Icon.png b/Reloaded/docs/Images/Nexus-Icon.png new file mode 100644 index 0000000..3e611b4 Binary files /dev/null and b/Reloaded/docs/Images/Nexus-Icon.png differ diff --git a/Reloaded/docs/Images/Reloaded-Heart-40.png b/Reloaded/docs/Images/Reloaded-Heart-40.png new file mode 100644 index 0000000..6fdf9da Binary files /dev/null and b/Reloaded/docs/Images/Reloaded-Heart-40.png differ diff --git a/Reloaded/docs/Images/Reloaded-Heart.png b/Reloaded/docs/Images/Reloaded-Heart.png new file mode 100644 index 0000000..0ed75b3 Binary files /dev/null and b/Reloaded/docs/Images/Reloaded-Heart.png differ diff --git a/Reloaded/docs/Images/Reloaded-Icon-40.png b/Reloaded/docs/Images/Reloaded-Icon-40.png new file mode 100644 index 0000000..9a43d61 Binary files /dev/null and b/Reloaded/docs/Images/Reloaded-Icon-40.png differ diff --git a/Reloaded/docs/Images/Reloaded-Icon.png b/Reloaded/docs/Images/Reloaded-Icon.png new file mode 100644 index 0000000..ae0d6d2 Binary files /dev/null and b/Reloaded/docs/Images/Reloaded-Icon.png differ diff --git a/Reloaded/docs/Images/Reloaded-Templates.7z b/Reloaded/docs/Images/Reloaded-Templates.7z new file mode 100644 index 0000000..371a063 Binary files /dev/null and b/Reloaded/docs/Images/Reloaded-Templates.7z differ diff --git a/Reloaded/docs/Pages/contributing/index.html b/Reloaded/docs/Pages/contributing/index.html new file mode 100644 index 0000000..5ed8dd5 --- /dev/null +++ b/Reloaded/docs/Pages/contributing/index.html @@ -0,0 +1,975 @@ + + + + + + + + + + + + + + + + + + + + Contributing to the Wiki: Locally - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Contributing to the Wiki: Locally

+
+

Info

+

This page shows you how to contribute to any documentation page or wiki +based on this template.

+
+
+

Note

+

This theme is forked from my theme for Nexus Docs; +and this page is synced with that.

+
+

Tutorial

+
+

Note

+

If you are editing the repository with the theme itself on Windows, it might be a good idea to run +git config core.symlinks true first to allow git to create symlinks on clone.

+
+

You should learn the basics of git, an easy way is to give GitHub Desktop (Tutorial) a go.
+It's only 15 minutes 😀.

+
    +
  1. Create a GitHub account.
  2. +
  3. +

    Fork this repository:

    +

    Image

    +

    This will create a copy of the repository on your own user account, which you will be able to edit.

    +
  4. +
  5. +

    Clone this repository.

    +

    For example, using GitHub Desktop: +Image

    +
  6. +
  7. +

    Make changes inside the docs folder.

    +

    Image

    +

    Consider using a Markdown Cheat Sheet if you are new to markdown.

    +

    I recommend using a markdown editor such as Typora.
    +Personally I just work from inside Rider.

    +
  8. +
  9. +

    Commit the changes and push to GitHub.

    +
  10. +
  11. +

    Open a Pull Request.

    +

    Image

    +

    Opening a Pull Request will allow us to review your changes before adding them with the main official page. If everything's good, we'll hit the merge button and add your changes to the official repository.

    +
  12. +
+

Website Live Preview

+

If you are working on the wiki locally, you can generate a live preview the full website. +Here's a quick guide of how you could do it from your command prompt (cmd).

+
    +
  1. +

    Install Python 3

    +

    If you have winget installed, or Windows 11, you can do this from the command prompt.

    +
    +
    +
    +
    winget install Python.Python.3
    +
    +
    +
    +
    pacman -S python-pip # you should already have Python
    +
    +
    +
    +
    +

    Otherwise download Python 3 from the official website or package manager.

    +
  2. +
  3. +

    Install Material for MkDocs and Plugins (Python package)

    +
    +
    +
    +
    # Restart your command prompt before running this command.
    +pip install mkdocs-material
    +pip install mkdocs-redirects
    +
    +
    +
    +

    On Linux, there is a chance that python might be a core part of your OS, meaning +that you ideally shouldn't touch the system installation.

    +

    Use virtual environments instead.

    +
    python -m venv mkdocs # Create the environment
    +source ~/mkdocs/bin/activate # Enter the environment
    +
    +pip install mkdocs-material
    +pip install mkdocs-redirects
    +
    +

    Make sure you enter the environment before any time you run mkdocs.

    +
    +
    +
    +
  4. +
  5. +

    Open a command prompt in the folder containing mkdocs.yml. and run the site locally. +

    # Move to project folder.
    +cd <Replace this with full path to folder containing `mkdocs.yml`>
    +mkdocs serve
    +

    +

    Image

    +

    Copy the address to your web browser and enjoy the live preview; any changes you save will be shown instantly.

    +
  6. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/docs/Pages/index.html b/Reloaded/docs/Pages/index.html new file mode 100644 index 0000000..f0960d7 --- /dev/null +++ b/Reloaded/docs/Pages/index.html @@ -0,0 +1,1021 @@ + + + + + + + + + + + + + + + + + + + + Index - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + + +

Index

+ +
+

The Reloaded MkDocs Theme

+ +

+ A Theme for MkDocs Material. +
+ That resembles the look of Reloaded. +
+ +

About

+

This it the NexusMods theme for Material-MkDocs, inspired by the look of Reloaded-II.

+

The overall wiki theme should look fairly close to the actual launcher appearance.

+

Setup From Scratch

+
    +
  • Add this repository as submodule to docs/Reloaded.
  • +
  • Save the following configuration as mkdocs.yml in your repository root.
  • +
+
site_name: Reloaded MkDocs Theme
+site_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+
+repo_name: Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+repo_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2
+
+extra:
+  social:
+    - icon: fontawesome/brands/github
+      link: https://github.com/Reloaded-Project
+    - icon: fontawesome/brands/twitter
+      link: https://twitter.com/thesewer56?lang=en-GB
+
+extra_css:
+  - Reloaded/Stylesheets/extra.css
+
+markdown_extensions:
+  - admonition
+  - tables
+  - pymdownx.details
+  - pymdownx.highlight
+  - pymdownx.superfences:
+      custom_fences:
+        - name: mermaid
+          class: mermaid
+          format: !!python/name:pymdownx.superfences.fence_code_format
+  - pymdownx.tasklist
+  - def_list
+  - meta
+  - md_in_html
+  - attr_list
+  - footnotes
+  - pymdownx.tabbed:
+      alternate_style: true
+  - pymdownx.emoji:
+      emoji_index: !!python/name:materialx.emoji.twemoji
+      emoji_generator: !!python/name:materialx.emoji.to_svg
+
+theme:
+  name: material
+  palette:
+    scheme: reloaded-slate
+  features:
+    - navigation.instant
+
+plugins:
+  - search
+
+nav:
+  - Home: index.md
+
+
    +
  • Add a GitHub Actions workload in .github/workflows/DeployMkDocs.yml.
  • +
+
name: DeployMkDocs
+
+# Controls when the action will run. 
+on:
+  # Triggers the workflow on push on the master branch
+  push:
+    branches: [ main ]
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  build:
+    # The type of runner that the job will run on
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - name: Checkout Branch
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+
+      # Deploy MkDocs
+      - name: Deploy MkDocs
+        # You may pin to the exact commit or the version.
+        # uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb
+        uses: mhausenblas/mkdocs-deploy-gh-pages@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          REQUIREMENTS: ./docs/requirements.txt
+
+
    +
  • Push to GitHub, this should produce a GitHub Pages site.
  • +
  • Go to Settings -> Pages in your repo and select gh-pages branch to enable GitHub pages.
  • +
+

Your page should then be live.

+
+

Tip

+

Refer to Contributing for instructions on how to locally edit and modify the wiki.

+
+
+

Note

+

For Reloaded3 theme use reloaded3-slate instead of reloaded-slate.

+
+

Extra

+
+

Info

+

Most documentation pages will also include additional plugins; some which are used in the pages here.
+Here is a sample complete mkdocs.yml you can copy to your project for reference.

+
+

Technical Questions

+

If you have questions/bug reports/etc. feel free to Open an Issue.

+

Happy Documenting ❤️

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/docs/Pages/license/index.html b/Reloaded/docs/Pages/license/index.html new file mode 100644 index 0000000..5d5c7af --- /dev/null +++ b/Reloaded/docs/Pages/license/index.html @@ -0,0 +1,1065 @@ + + + + + + + + + + + + + + + + + + + + The Reloaded Project License - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

The Reloaded Project License

+
+

Most components of the Reloaded are governed by the GPLv3 license.

+
+
+

In some, albeit rare scenarios, certain libraries might be licensed under LGPLv3 instead.

+
+

This is a FAQ meant to clarify the licensing choice and its implications. +Please note, though, that the full license text is the final legal authority.

+

Why was GPL v3 chosen?

+
+

The primary objective is to prevent closed-source, commercial exploitation of the project.

+
+

We want to ensure that the project isn't used within a proprietary environment for +profit-making purposes such as:

+
    +
  • Being sold behind a Patreon paywall.
  • +
  • Being integrated into a closed-source commercial product for sale.
  • +
+
+

The Reloaded Project is a labour of love from unpaid hobbyist volunteers.

+
+

Exploiting that work for profit feels fundamentally unfair.

+

While the GPLv3 license doesn't prohibit commercial use outright, it does prevent commercial +exploitation by requiring that contributions are given back to the open-source community.

+

In that fashion, everyone can benefit from the projects under the Reloaded label.

+

Can I use Reloaded Libraries Commercially?

+

You can as long as the resulting produce is also licensed under GPLv3, and thus open source.

+

Can I use Reloaded Libraries in a closed-source application?

+
+

The license terms do not permit this.

+
+

However, if your software is completely non-commercial, meaning it's neither +sold for profit, funded in development, nor hidden behind a paywall (like Patreon), +we probably just look the other way.

+

This often applies to non-professional programmers, learners, or those +with no intent to exploit the project. We believe in understanding and +leniency for those who might not know better.

+

GPL v3 exists to protect the project and its contributors. If you're not exploiting the project for commercial +gain, you're not hurting us; and we will not enforce the terms of the GPL.

+

If you are interested in obtaining a commercial license, or want an explicit written exemption, +please get in touch with the repository owners.

+ +

Yes, as long as you adhere to the GPLv3 license terms, you're permitted to statically +link Reloaded Libraries into your project, for instance, through the use of NativeAOT or ILMerge.

+

Guidelines for Non-Commercial Use

+

We support and encourage the non-commercial use of Reloaded Libraries. +Non-commercial use generally refers to the usage of our libraries for personal projects, +educational purposes, academic research, or use by non-profit organizations.

+

Personal Projects

+

You're free to use our libraries for projects that you undertake +for your own learning, hobby or personal enjoyment. This includes creating mods for your +favorite games or building your own applications for personal use.

+

Educational Use

+

Teachers and students are welcome to use our libraries as a learning +resource. You can incorporate them into your teaching materials, student projects, coding +bootcamps, workshops, etc.

+

Academic Research

+

Researchers may use our libraries for academic and scholarly research. +We'd appreciate if you cite our work in any publications that result from research involving our libraries.

+

Non-profit Organizations

+

If you're part of a registered non-profit organization, +you can use our libraries in your projects. However, any derivative work that uses our +libraries must also be released under the GPL.

+

Please remember, if your usage of our libraries evolves from non-commercial to commercial, +you must ensure compliance with the terms of the GPL v3 license.

+

Attribution Requirements

+

As Reloaded Project is a labor of love, done purely out of passion and with an aim to contribute +to the broader community, we highly appreciate your support in providing attribution when using +our libraries.

+

While not legally mandatory under GPL v3, it is a simple act that can go a long +way in recognizing the efforts of our contributors and fostering an open and collaborative atmosphere.

+

If you choose to provide attribution (and we hope you do!), here are some guidelines:

+
    +
  • +

    Acknowledge the Use of Reloaded Libraries: Mention that your project uses or is based on Reloaded libraries. + This could be in your project's readme, a credits page on a website, a manual, or within the software itself.

    +
  • +
  • +

    Link to the Project: If possible, provide a link back to the Reloaded Project. + This allows others to explore and potentially benefit from our work.

    +
  • +
+

Remember, attribution is more than just giving credit,,, it's a way of saying thank you 👉👈, fostering reciprocal +respect, and acknowledging the power of collaborative open-source development.

+

We appreciate your support and look forward to seeing what amazing projects you create using Reloaded libraries!

+

Code from MIT/BSD Licensed Projects

+

In some rare instances, code from more permissively licensed projects, such as those under the +MIT or BSD licenses, may be referenced, incorporated, or slightly modified within the Reloaded Project.

+

It's important to us to respect the terms and intentions of these permissive licenses, +which often allow their code to be used in a wide variety of contexts, including in GPL-licensed projects like ours.

+

In these cases, the Reloaded Project is committed to clearly disclosing the usage of such code:

+
    +
  • +

    Method-Level Disclosure: For individual methods or small code snippets, we use appropriate + attribution methods, like programming language attributes. For example, methods borrowed or adapted + from MIT-licensed projects might be marked with a [MITLicense] attribute.

    +
  • +
  • +

    File-Level Disclosure: For larger amounts of code, such as entire files or modules, we'll include + the original license text at the top of the file and clearly indicate which portions of the code originate + from a differently-licensed project.

    +
  • +
  • +

    Project-Level Disclosure: If an entire library or significant portion of a project under a more permissive + license is used, we will include an acknowledgment in a prominent location, such as the readme file or the + project's license documentation.

    +
  • +
+

This approach ensures we honor the contributions of the open source community at large, respect the original +licenses, and maintain transparency with our users about where code originates from.

+

Any files/methods or snippets marked with those attributes may be consumed using their original license terms.

+

i.e. If a method is marked with [MITLicense], you may use it under the terms of the MIT license.

+

Contributing to the Reloaded Project

+

We welcome and appreciate contributions to the Reloaded Project! +By contributing, you agree to share your changes under the same GPLv3 license, +helping to make the project better for everyone.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/docs/Pages/testing-zone/index.html b/Reloaded/docs/Pages/testing-zone/index.html new file mode 100644 index 0000000..0725447 --- /dev/null +++ b/Reloaded/docs/Pages/testing-zone/index.html @@ -0,0 +1,1092 @@ + + + + + + + + + + + + + + + + + + + + Testing Zone - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Testing Zone

+
+

Info

+

This is a dummy page with various Material MkDocs controls and features scattered throughout for testing.

+
+

Custom Admonitions

+
+

Reloaded Admonition

+

An admonition featuring a Reloaded logo. +My source is in Stylesheets/extra.css as Custom 'reloaded' admonition.

+
+
+

Heart Admonition

+

An admonition featuring a heart; because we want to contribute back to the open source community.
+My source is in Stylesheets/extra.css as Custom 'reloaded heart' admonition.

+
+
+

Nexus Admonition

+

An admonition featuring a Nexus logo. +My source is in Stylesheets/extra.css as Custom 'nexus' admonition.

+
+
+

Heart Admonition

+

An admonition featuring a heart; because we want to contribute back to the open source community.
+My source is in Stylesheets/extra.css as Custom 'nexus heart' admonition.

+
+

Mermaid Diagram

+

Flowchart (Source: Nexus Archive Library):

+
flowchart TD
+    subgraph Block 2
+        BigFile1.bin
+    end
+
+    subgraph Block 1
+        BigFile0.bin
+    end
+
+    subgraph Block 0
+        ModConfig.json -.-> Updates.json 
+        Updates.json -.-> more["... more .json files"]        
+    end
+

Sequence Diagram (Source: Reloaded3 Specification):

+
sequenceDiagram
+
+    % Define Items
+    participant Mod Loader
+    participant Virtual FileSystem (VFS)
+    participant CRI CPK Archive Support
+    participant Persona 5 Royal Support
+    participant Joker Costume
+
+    % Define Actions
+    Mod Loader->>Persona 5 Royal Support: Load Mod
+    Persona 5 Royal Support->>Mod Loader: Request CRI CPK Archive Support API
+    Mod Loader->>Persona 5 Royal Support: Receive CRI CPK Archive Support Instance
+
+    Mod Loader->>Joker Costume: Load Mod
+    Mod Loader-->Persona 5 Royal Support: Notification: 'Loaded Joker Costume'
+    Persona 5 Royal Support->>CRI CPK Archive Support: Add Files from 'Joker Costume' to CPK Archive (via API)
+

State Diagram (Source: Mermaid Docs):

+
stateDiagram-v2
+    [*] --> Still
+    Still --> [*]
+
+    Still --> Moving
+    Moving --> Still
+    Moving --> Crash
+    Crash --> [*]
+

Class Diagram (Arbitrary)

+
classDiagram
+    class Animal
+    `NexusMobile™` <|-- Car
+
+

Note

+

At time of writing, version of Mermaid is a bit outdated here; and other diagrams might not render correctly +(even on unmodified theme); thus certain diagrams have been omitted from here.

+
+

Code Block

+

Snippet from C# version of Sewer's Virtual FileSystem (VFS):

+
/// <summary>
+/// Tries to get files for a specific folder, assuming the input path is already in upper case.
+/// </summary>
+/// <param name="folderPath">The folder to find. Already lowercase.</param>
+/// <param name="value">The returned folder instance.</param>
+/// <returns>True if found, else false.</returns>
+[MethodImpl(MethodImplOptions.AggressiveInlining)]
+public bool TryGetFolderUpper(ReadOnlySpan<char> folderPath, out SpanOfCharDict<TTarget> value)
+{
+    // Must be O(1)
+    value = default!;        
+
+    // Compare equality.
+    // Note to devs: Do not invert branches, we optimise for hot paths here.
+    if (folderPath.StartsWith(Prefix))
+    {
+        // Check for subfolder in branchless way.
+        // In CLR, bool is length 1, so conversion to byte should be safe.
+        // Even suppose it is not; as long as code is little endian; truncating int/4 bytes to byte still results 
+        // in correct answer.
+        var hasSubfolder = Prefix.Length != folderPath.Length;
+        var hasSubfolderByte = Unsafe.As<bool, byte>(ref hasSubfolder);
+        var nextFolder = folderPath.SliceFast(Prefix.Length + hasSubfolderByte);
+
+        return SubfolderToFiles.TryGetValue(nextFolder, out value!);
+    }
+
+    return false;
+}
+
+

Something more number heavy, Fast Inverse Square Root from Quake III Arena (unmodified). +

float Q_rsqrt( float number )
+{
+    long i;
+    float x2, y;
+    const float threehalfs = 1.5F;
+
+    x2 = number * 0.5F;
+    y  = number;
+    i  = * ( long * ) &y;                       // evil floating point bit level hacking
+    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? 
+    y  = * ( float * ) &i;
+    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
+//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
+
+    return y;
+}
+

+

Default Admonitions

+
+

Note

+

Test

+
+
+

Abstract

+

Test

+
+
+

Info

+

Test

+
+
+

Tip

+

Test

+
+
+

Success

+

Test

+
+
+

Question

+

Test

+
+
+

Warning

+

Test

+
+
+

Failure

+

Test

+
+
+

Danger

+

Test

+
+
+

Bug

+

Test

+
+
+

Example

+

Test

+
+
+

Quote

+

Test

+
+

Tables

+ + + + + + + + + + + + + + + + + + + + + +
MethodDescription
GET Fetch resource
PUT Update resource
DELETE Delete resource
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/Reloaded/docs/Stylesheets/Montserrat-Bold.ttf b/Reloaded/docs/Stylesheets/Montserrat-Bold.ttf new file mode 100644 index 0000000..efddc83 Binary files /dev/null and b/Reloaded/docs/Stylesheets/Montserrat-Bold.ttf differ diff --git a/Reloaded/docs/Stylesheets/Montserrat-Regular.ttf b/Reloaded/docs/Stylesheets/Montserrat-Regular.ttf new file mode 100644 index 0000000..aa9033a Binary files /dev/null and b/Reloaded/docs/Stylesheets/Montserrat-Regular.ttf differ diff --git a/Reloaded/docs/Stylesheets/Montserrat-SemiBold.ttf b/Reloaded/docs/Stylesheets/Montserrat-SemiBold.ttf new file mode 100644 index 0000000..cbf44db Binary files /dev/null and b/Reloaded/docs/Stylesheets/Montserrat-SemiBold.ttf differ diff --git a/Reloaded/docs/Stylesheets/extra.css b/Reloaded/docs/Stylesheets/extra.css new file mode 100644 index 0000000..b183a12 --- /dev/null +++ b/Reloaded/docs/Stylesheets/extra.css @@ -0,0 +1,597 @@ +/* + Nexus Mods Theme +*/ + +:root { + --md-admonition-icon--nexus: url('../Images/Nexus-Icon-40.png'); + --md-admonition-icon--nexusheart: url('../Images/Nexus-Heart-40.png'); + --md-admonition-icon--reloaded: url('../Images/Reloaded-Icon-40.png'); + --md-admonition-icon--reloadedheart: url('../Images/Reloaded-Heart-40.png'); +} + +/* Slate theme, i.e. dark mode */ + +[data-md-color-scheme="nexus-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 217, 143, 64; + + /* Primary color shades */ + --md-primary-fg-color: #D98F40; + --md-primary-fg-color--light: #E0A362; + --md-primary-fg-color--dark: #C87B28; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #C87B28; + --md-accent-fg-color--transparent: #C87B2810; + + --md-accent-bg-color: #2A2C2B; + --md-accent-bg-color--light: #2A2C2B; + +/* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. +*/ + --md-hue: 31; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.12); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="nexus-slate"] .md-header { + background-color: var(--nexus-background-primary); +} + +[data-md-color-scheme="reloaded-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 182, 99, 99; + + /* Primary color shades */ + --md-primary-fg-color: #793939; + --md-primary-fg-color--light: #B66363; + --md-primary-fg-color--lightest: #d6a8a8; + --md-primary-fg-color--dark: #572929; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #793939; + --md-accent-fg-color--transparent: #79393960; + + --md-accent-bg-color: #181818; + --md-accent-bg-color--light: #181818; + + /* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. + */ + --md-hue: 0; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.04); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color--lightest); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="reloaded-slate"] .md-typeset a:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav--primary .md-nav__item--active>.md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + + +[data-md-color-scheme="reloaded3-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 63, 153, 217; + + /* Primary color shades */ + --md-primary-fg-color: #fa7774; + --md-primary-fg-color--light: #fc918b; + --md-primary-fg-color--dark: #e96060; + --md-primary-fg-color--darker: #d74f52; + --md-primary-fg-color--darkest: #c53e44; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #e96060; + --md-accent-fg-color--transparent: #e9606030; + + --md-accent-bg-color: #2A2C2B; + --md-accent-bg-color--light: #2A2C2B; + + /* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. + */ + --md-hue: 1; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.12); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-header { + background-color: var(--md-primary-fg-color--darker); +} + +@media screen and (max-width: 76.1875em) { + /* Title in Drawer */ + [data-md-color-scheme="reloaded3-slate"] .md-nav--primary .md-nav__title[for=__drawer] { + background-color: var(--md-primary-fg-color--darker); + color: var(--md-primary-bg-color); + font-weight: 700; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-nav__source { + background-color: var(--md-primary-fg-color--darkest); + color: var(--md-primary-bg-color); +} + +.md-nav__title { + color: var(--md-default-fg-color); +} + +.md-nav__link { + color: var(--md-default-fg-color--light); +} + +/* Custom 'nexus' admonition */ +.md-typeset .admonition.nexus, +.md-typeset details.nexus { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexus > .admonition-title, +.md-typeset .nexus > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexus > .admonition-title::before, +.md-typeset .nexus > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexus); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.nexusheart, +.md-typeset details.nexusheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexusheart > .admonition-title, +.md-typeset .nexusheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexusheart > .admonition-title::before, +.md-typeset .nexusheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexusheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'reloaded' admonition */ + +.md-typeset .admonition.reloaded, +.md-typeset details.reloaded { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloaded > .admonition-title, +.md-typeset .reloaded > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloaded > .admonition-title::before, +.md-typeset .reloaded > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloaded); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.reloadedheart, +.md-typeset details.reloadedheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloadedheart > .admonition-title, +.md-typeset .reloadedheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloadedheart > .admonition-title::before, +.md-typeset .reloadedheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloadedheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Add Title Font */ +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-Regular.ttf") format("truetype"); + font-weight: normal; +} + +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-SemiBold.ttf") format("truetype"); + font-weight: 600; +} + +@font-face { + font-family: "Montserrat"; + src: url("./Montserrat-Bold.ttf") format("truetype"); + font-weight: bold; +} + +/* Headers */ +[data-md-color-scheme="nexus-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} \ No newline at end of file diff --git a/Reloaded/mkdocs.yml b/Reloaded/mkdocs.yml new file mode 100644 index 0000000..bf04100 --- /dev/null +++ b/Reloaded/mkdocs.yml @@ -0,0 +1,58 @@ +site_name: Reloaded MkDocs Theme +site_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2 + +repo_name: Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2 +repo_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2 + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/Reloaded-Project + - icon: fontawesome/brands/twitter + link: https://twitter.com/thesewer56?lang=en-GB + +extra_css: + - Stylesheets/extra.css + +markdown_extensions: + - admonition + - tables + - pymdownx.details + - pymdownx.highlight + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tasklist + - def_list + - meta + - md_in_html + - attr_list + - footnotes + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + +theme: + name: material + palette: + scheme: reloaded3-slate + features: + - navigation.instant + +plugins: + - search + - redirects: + redirect_maps: + 'index.md': 'Pages/index.md' + +nav: + - Home: Pages/index.md + - License: Pages/license.md + - Contributing: Pages/contributing.md + - Testing Zone: Pages/testing-zone.md + +index: Pages/index.md \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.51d95adb.min.js b/assets/javascripts/bundle.51d95adb.min.js new file mode 100644 index 0000000..b20ec68 --- /dev/null +++ b/assets/javascripts/bundle.51d95adb.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(kt)for(var r of kt(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Ht((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,We=T.tagName;return!!(We==="INPUT"&&a[Ke]&&!T.readOnly||We==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function re(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,re())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Ht(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(re,z){d.append(z,re)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,re=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,re.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Ht((Lt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},re=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=re;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},We=Ke;function Ie(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(w){return typeof w}:Ie=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Ie(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Ie(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",Rt=We({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Le,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(Rt){Le=Le&&!!document.queryCommandSupported(Rt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Xe?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=Fe.EMPTY;function It(e){return e instanceof Fe||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new Fe(function(){n.currentObservers=null,De(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ne(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ne(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var On=function(e){ne(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Dt(kr(e))?e.pop():void 0}function Vt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ki();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return ln(this,arguments,function(){var r,n,o,i;return Pt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Xe(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Xe(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Xe(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(Nt(e))return Qi(e);if(pt(e))return Yi(e);if(zt(e))return Gi(e);if(qt(e))return _n(e);if(Yt(e))return Bi(e);if(Bt(e))return Ji(e)}throw Kt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):me,Oe(1),r?He(t):zn(function(){return new Xt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function fe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},re=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;re(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var We=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),We.subscribe(Ke),!u&&d>0&&(u=new et({next:function(Ie){return We.next(Ie)},error:function(Ie){v=!0,B(),p=jr(re,o,Ie),We.error(Ie)},complete:function(){h=!0,B(),p=jr(re,a),We.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),Y())}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,we),l(()=>Be(e)),N(Be(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,we),l(()=>rr(e)),N(rr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>de(e)))),N(de(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function sr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=de(e),o=bt(e);return r>=o.height-n.height-t}),Y())}var cr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return cr[e].checked}function qe(e,t){cr[e].checked!==t&&cr[e].click()}function je(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),fe());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>se(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function ur(e,t={credentials:"same-origin"}){return ve(fetch(`${e}`,t)).pipe(ce(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ur(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return ur(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Be(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Contribution Guidelines

+
+

The wiki provides details on internals of the library. They may help you when contributing 😉.

+
+

First off, thank you for considering contributing to reloaded-hooks.

+

If your contribution is not straightforward, please first discuss the change you +wish to make by creating a new issue before making the change. We might be able to discuss +general design, etc. before you embark on a huge endeavour.

+

Reporting Issues

+

Before reporting an issue on the +issue tracker, +please check that it has not already been reported by searching for some related +keywords.

+

Pull Requests

+

Try to do one pull request per change.

+

Commit Names

+

Reloaded repositories auto-generate changelogs based on commit names.

+

When you make git commits; try to stick to the style of Keep a changelog:

+
    +
  • Added for new features.
  • +
  • Changed for changes in existing functionality.
  • +
  • Deprecated for soon-to-be removed features.
  • +
  • Removed for now removed features.
  • +
  • Fixed for any bug fixes.
  • +
  • Security in case of vulnerabilities.
  • +
+

Code Style

+

Please use the standard code style cargo fmt, and run the clippy linter +(cargo clippy), fixing warnings before submitting PRs.

+

If you are using VSCode, this should be automated (on Save) per this repository's settings.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/arm64/aarch64/index.html b/dev/arch/arm64/aarch64/index.html new file mode 100644 index 0000000..9b1d22f --- /dev/null +++ b/dev/arch/arm64/aarch64/index.html @@ -0,0 +1,1090 @@ + + + + + + + + + + + + + + + + + + + + + + + + Overview - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

ARM64

+
+

This is just a quick reference sheet for developers.

+
+
+

ARM64 is not currently implemented.

+
+
    +
  • Code Alignment: 4 bytes
  • +
+

Registers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RegisterARM64 (System V)Volatile/Non-Volatile
x0-x7Parameter/Result RegistersVolatile
x8Indirect result location registerVolatile
x9-x15Local VariablesVolatile
x16-x17Intra-procedure-call scratch registersVolatile
x18Platform register, conventionally the TLS baseVolatile
x19-x28Registers saved across function callsNon-Volatile
x29Frame pointerNon-Volatile
x30Link registerVolatile
spStack pointerNon-Volatile
xzrZero register, always reads as zeroN/A
x31Stack pointer or zero register, contextually reads as either sp or xzrN/A
+

For floating point / SIMD registers:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
RegisterARM64 (System V)Volatile/Non-Volatile
v0-v7Parameter/Result registersVolatile
v8-v15Temporary registersVolatile
v16-v31Registers saved across function callsNon-Volatile
+

Calling Convention Inference

+
+

It is recommended library users manually specify conventions in their hook functions."

+
+

When the calling convention of <your function> is not specified, wrapper libraries must insert +the appropriate default convention in their wrappers.

+

Rust

+
    +
  • aarch64-unknown-linux-gnu: SystemV
  • +
  • aarch64-pc-windows-msvc: Windows ARM64
  • +
+

C

+
    +
  • Linux ARM64: SystemV
  • +
  • Windows ARM64: Windows ARM64
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/arm64/code_relocation/index.html b/dev/arch/arm64/code_relocation/index.html new file mode 100644 index 0000000..d46d8aa --- /dev/null +++ b/dev/arch/arm64/code_relocation/index.html @@ -0,0 +1,1341 @@ + + + + + + + + + + + + + + + + + + + + + + + + Code Relocation - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Code Relocation

+
+

This page provides a listing of all instructions rewritten as part of the Code Relocation process.

+
+

ADR(P)

+

Purpose:

+

The ADR instruction in ARM architectures computes the address of a label and writes it to the destination register.

+

Behaviour:

+

The ADR(P) instruction is rewritten as one of the following:
+- ADR(P)
+- ADR(P) + ADD
+- MOV (1-4 instructions)

+

Example:

+
    +
  1. +

    From ADRP to ADR: +

    // Before: ADRP x0, 0x101000
    +// After: ADR x0, 0xFFFFF
    +// Parameters: (old_instruction, old_address, new_address)
    +rewrite_adr(0x000800B0_u32.to_be(), 0, 4097);
    +

    +
  2. +
  3. +

    Within 4GiB Range with Offset: +

    // Before: ADRP x0, 0x101000
    +// After: 
    +//  - ADRP x0, 0x102000
    +//  - ADD x0, x0, 1
    +rewrite_adr(0x000800B0_u32.to_be(), 4097, 0);
    +

    +
  4. +
  5. +

    Within 4GiB Range without Offset: +

    // Before: ADRP x0, 0x101000
    +// After: ADRP x0, 0x102000
    +rewrite_adr(0x000800B0_u32.to_be(), 4096, 0);
    +

    +
  6. +
  7. +

    Out of Range: +

    // PC = 0x100000000
    +
    +// Before: ADRP, x0, 0x101000
    +// After: MOV IMMEDIATE 0x100101000
    +rewrite_adr(0x000800B0_u32.to_be(), 0x100000000, 0);
    +

    +
  8. +
+

Branch (Conditional)

+

Purpose:
+The Bcc instruction in ARM architectures performs a conditional branch based on specific condition flags.

+

Behaviour:
+The Branch Conditional instruction is rewritten as:
+- BCC
+- BCC + [B]
+- BCC + [ADRP + ADD + BR]
+- BCC + [MOV to Register + Branch Register]

+

<skip> means, invert the condition, and jump over the code inside [] brackets.

+

Example:

+
    +
  1. +

    Within 1MiB: +

    // Before: b.eq #4
    +// After: b.eq #-4092
    +// Parameters: (old_instruction, old_address, new_address, scratch_register)
    +rewrite_bcc(0x20000054_u32.to_be(), 0, 4096, Some(17));
    +

    +
  2. +
  3. +

    Within 128MiB: +

    // Before: b.eq #0
    +// After: 
    +//   - b.ne #8 
    +//   - b #-0x80000000
    +rewrite_bcc(0x00000054_u32.to_be(), 0, 0x8000000 - 4, Some(17));
    +

    +
  4. +
  5. +

    Within 4GiB Range with Address Adjustment: +

    // Before: b.eq #512
    +// After: 
    +//   - b.ne #16 
    +//   - adrp x17, #0x8000000
    +//   - add x17, #512
    +//   - br x17
    +rewrite_bcc(0x00100054_u32.to_be(), 0x8000000, 0, Some(17));
    +

    +
  6. +
  7. +

    Within 4GiB Range without Offset: +

    // Before: b.eq #512
    +// After: 
    +//   - b.ne #12
    +//   - adrp x17, #-0x8000000 
    +//   - br x17
    +rewrite_bcc(0x00100054_u32.to_be(), 0, 0x8000000, Some(17));
    +

    +
  8. +
  9. +

    Last Resort: +

    // Before: b.eq #0
    +// After: 
    +//   - b.ne #12
    +//   - movz x17, #0 
    +//   - br x17
    +rewrite_bcc(0x00000054_u32.to_be(), 0, 0x100000000, Some(17));
    +

    +
  10. +
+

Branch

+
+

Including Branch+Link (BL).

+
+

Purpose:
+The B (or BL for Branch+Link) instruction in ARM architectures performs a direct branch (or branch with link) to a specified address. When using the BL variant, the return address (the address of the instruction following the branch) is stored in the link register LR.

+

Behaviour:
+The Branch instruction is rewritten as one of the following:
+- B (or BL)
+- ADRP + BR
+- ADRP + ADD + BR
+- MOV + BR

+

Example:

+
    +
  1. +

    Direct Branch within Range: +

    // Before: b #4096
    +// After: b #8192
    +// Parameters: (old_instruction, old_address, new_address, scratch_register, link)
    +rewrite_b(0x00040014_u32.to_be(), 8192, 4096, Some(17), false);
    +

    +
  2. +
  3. +

    Within 4GiB with Address Adjustment: +

    // Before: b #4096
    +// After: 
    +//   - adrp x17, #0x8000000
    +//   - br x17
    +rewrite_b(0x00040014_u32.to_be(), 0x8000000, 0, Some(17), false);
    +

    +
  4. +
  5. +

    Within 4GiB Range with Offset: +

    // Before: b #4096
    +// After: 
    +//   - adrp x17, #0x8000512
    +//   - add x17, x17, #512
    +//   - br x17
    +rewrite_b(0x00040014_u32.to_be(), 0x8000512, 0, Some(17), false);
    +

    +
  6. +
  7. +

    Out of Range, Use MOV: +

    // Before: b #4096
    +// After: 
    +//   - movz x17, #... 
    +//   - ...
    +//   - br x17
    +rewrite_b(0x00040014_u32.to_be(), 0x100000000, 0, Some(17), false);
    +

    +
  8. +
  9. +

    Branch with Link within Range: +

    // Before: bl #4096
    +// After: bl #8192
    +rewrite_b(0x00040094_u32.to_be(), 8192, 4096, Some(17), true);
    +

    +
  10. +
+

CBZ (Compare and Branch on Zero)

+

Purpose:
+The CBZ instruction in ARM architectures performs a conditional branch when the specified register is zero. If the register is not zero and the condition is not met, the next sequential instruction is executed.

+

Behaviour:
+The CBZ instruction is rewritten as one of the following:
+- CBZ
+- CBZ + [B]
+- CBZ + [ADRP + BR]
+- CBZ + [ADRP + ADD + BR]
+- CBZ + [MOV to Register + Branch Register]

+

Here, <skip> is used to invert the condition and jump over the set of instructions inside the [] brackets if the condition is not met.

+

Example:

+
    +
  1. +

    Within 1MiB Range: +

    // Before: cbz x0, #4096
    +// After: cbz x0, #8192
    +// Parameters: (old_instruction, old_address, new_address)
    +rewrite_cbz(0x008000B4_u32.to_be(), 8192, 4096, Some(17));
    +

    +
  2. +
  3. +

    Within 128MiB Range: +

    // Before: cbz x0, #4096
    +// After: 
    +//   - cbnz x0, #8
    +//   - b #0x8000000
    +rewrite_cbz(0x008000B4_u32.to_be(), 0x8000000, 4096, Some(17));
    +

    +
  4. +
  5. +

    Within 4GiB + 4096 aligned: +

    // Before: cbz x0, #4096
    +// After: 
    +//   - cbnz x0, <skip 3 instructions> 
    +//   - adrp x17, #0x8000000
    +//   - br x17
    +rewrite_cbz(0x008000B4_u32.to_be(), 0x8000000, 0, Some(17));
    +

    +
  6. +
  7. +

    Within 4GiB with Offset: +

    // Before: cbz x0, #4096
    +// After: 
    +//   - cbnz x0, <skip 4 instructions>
    +//   - adrp x17, #0x8000000
    +//   - add x17, #512
    +//   - br x17
    +rewrite_cbz(0x008000B4_u32.to_be(), 0x8000512, 0, Some(17));
    +

    +
  8. +
  9. +

    Out of Range (Move and Branch): +

    // Before: cbz x0, #4096
    +// After: 
    +//   - cbnz x0, <skip X instructions> 
    +//   - mov x17, <immediate address>
    +//   - br x17
    +rewrite_cbz(0x008000B4_u32.to_be(), 0x100000000, 0, Some(17));
    +

    +
  10. +
+

LDR (Load Register)

+
+

This includes Prefetch PRFM which shares opcode with LDR.

+
+

Purpose:
+The LDR instruction in ARM architectures is used to load a value from memory into a register. It can use various addressing modes, but commonly it involves an offset from a base register or the program counter.

+

Behaviour:
+The LDR instruction is rewritten as one of the following, depending on the relocation range:

+
    +
  • LDR Literal
  • +
  • ADRP + LDR (with Unsigned Offset)
  • +
  • MOV Address to Register + LDR
  • +
+

The choice of rewriting strategy is based on the distance between the old address and the new one, with a preference for the most direct form that satisfies the required address range.

+

If the instruction is Prefetch PRFM, it is discarded if it can't be re-encoded as PRFM (literal), as prefetching with multiple instructions is probably less efficient than not prefetching at all.

+

Example:

+
    +
  1. +

    Within 1MiB Range: +

    // Before: LDR x0, #0
    +// After: LDR x0, #4096
    +// Parameters: (opcode, new_imm12, rn)
    +rewrite_ldr_literal(0x00000058_u32.to_be(), 4096, 0);
    +

    +
  2. +
  3. +

    Within 4GiB + 4096 aligned: +

    // Before: LDR x0, #0
    +// After: 
    +//   - adrp x0, #0x100000
    +//   - ldr x0, [x0]
    +// Parameters: (opcode, new_address, old_address)
    +rewrite_ldr_literal(0x00000058_u32.to_be(), 0x100000, 0);
    +

    +
  4. +
  5. +

    Within 4GiB: +

    // Before: LDR x0, #512
    +// After: 
    +//   - adrp x0, #0x100000
    +//   - ldr x0, [x0, #512]
    +// Parameters: (opcode, new_address, old_address)
    +rewrite_ldr_literal(0x00100058_u32.to_be(), 0x100000, 0);
    +

    +
  6. +
  7. +

    Out of Range (Last Resort): +

    // Before: LDR x0, #512
    +// After: 
    +//   - movz x0, #0, lsl #16
    +//   - movk x0, #0x1, lsl #32
    +//   - ldr x0, [x0, #512]
    +// Parameters: (opcode, new_address, old_address)
    +rewrite_ldr_literal(0x00100058_u32.to_be(), 0x100000000, 0);
    +

    +
  8. +
+

TBZ (Test and Branch on Zero)

+

Purpose:
+The TBZ instruction in ARM architectures tests a specified bit in a register and performs a conditional branch if the bit is zero. If the tested bit is not zero, the next sequential instruction is executed.

+

Behaviour:
+The TBZ instruction is rewritten based on the distance to the new branch target. It is transformed into one of the following patterns:
+- TBZ
+- TBZ + B
+- TBZ + ADRP + BR
+- TBZ + ADRP + ADD + BR
+- TBZ + MOV to Register + Branch Register

+

Here, <skip> is used to indicate a conditional skip over a set of instructions if the tested bit is not zero. The specific transformation depends on the offset between the current position and the new branch target.

+

Safety:
+It is crucial to ensure that the provided instruction parameter is a valid TBZ opcode. Incorrect opcodes or assumptions that a different type of instruction is a TBZ may lead to undefined behaviour.

+

Functionality: +The rewrite_tbz function alters the TBZ instruction to accommodate a new target address that is outside of its original range. The target address could be within the same 32KiB range or farther, necessitating different rewriting strategies.

+

Example:

+
    +
  1. +

    Within 32KiB Range: +

    // Original: tbz x0, #0, #4096
    +// Rewritten: tbz x0, #0, #8192
    +// Parameters: (old_instruction, old_address, new_address, scratch_reg)
    +rewrite_tbz(0x00800036_u32.to_be(), 8192, 4096, Some(17));
    +

    +
  2. +
  3. +

    Within 128MiB Range: +

    // Original: tbz x0, #0, #4096
    +// Rewritten:
    +//   - tbnz x0, #0, #8
    +//   - b #0x8000000
    +rewrite_tbz(0x00800036_u32.to_be(), 0x8000000, 4096, Some(17));
    +

    +
  4. +
  5. +

    Within 4GiB Range Aligned to 4096: +

    // Original: tbz x0, #0, #4096
    +// Rewritten:
    +//   - tbnz w0, #0, #0xc
    +//   - adrp x17, #0x8001000
    +//   - br x17
    +rewrite_tbz(0x00800036_u32.to_be(), 0x8000000, 0, Some(17));
    +

    +
  6. +
  7. +

    Within 4GiB Range with Offset: +

    // Original: tbz x0, #0, #4096
    +// Rewritten:
    +//    - tbnz w0, #0, #0x10
    +//    - adrp x17, #0x8001000
    +//    - add x17, x17, #0x512
    +//    - br x17
    +rewrite_tbz(0x00800036_u32.to_be(), 0x8000512, 0, Some(17));
    +

    +
  8. +
  9. +

    Out of 4GiB Range (Move and Branch): +

    // Original: tbz x0, #0, #4096
    +// Rewritten:
    +//    - tbnz w0, #0, #0x14
    +//    - movz x17, #0x1000
    +//    - movk x17, #0, lsl #16
    +//    - movk x17, #0x1, lsl #32
    +//    - br x17
    +rewrite_tbz(0x00800036_u32.to_be(), 0x100000000, 0, Some(17));
    +

    +
  10. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/operations-impl/index.html b/dev/arch/operations-impl/index.html new file mode 100644 index 0000000..3fa8523 --- /dev/null +++ b/dev/arch/operations-impl/index.html @@ -0,0 +1,1825 @@ + + + + + + + + + + + + + + + + + + + + + + + + Operations (Implemented) - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Operations

+
+

This page tells you which Operations are currently implemented for each architecture.

+
+
    +
  • ❌ Means it is not implemented.
  • +
  • ✅ Means it is implemented.
  • +
  • ❓ Means 'not applicable'.
  • +
+

Needed for Basic Hooking Support

+

JumpRelative

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64+-2GiB
x86+-2GiB
ARM64 (+- 128MiB)+-128MiB
ARM64 (+- 4GiB)Uses 3 instructions. Used if within range.
+

JumpAbsolute

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64Uses scratch register for efficiency.
x86Uses scratch register for efficiency.
ARM64Uses scratch register (required)
+

JumpAbsoluteIndirect

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x86
x86
ARM64Variant 0.
ARM64Variant 1. Replaced with JumpAbsolute, for perf reasons.
+

Needed for Wrapper Generation

+

Mov

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureRegister to RegisterVector to Vector
x64
x86
ARM64
+

MovFromStack

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Architectureto Registerto Vector
x64
x86
ARM64
+

MovToStack

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Architectureto Registerto Vector
x64
x86
ARM64*
+
+

This is not needed for optimal code generation on ARM64, thus was not implemented.

+
+

Push

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureRegisterVector
x64
x86
ARM64
+

PushStack

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64
x86
ARM64Will use vector registers when available.
+

PushConstant

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64
x86
ARM642-5 instructions, depending on constant length.
+

StackAlloc

+ + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupported
x64
x86
ARM64
+

Pop

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Architectureto Registerto VectorNotes
x64
x86
ARM64
+

XChg

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureRegistersVectorsNotes
x64✅ **Requires scratch register
x86✅ **Requires scratch register
ARM64✅ *✅ **Requires scratch register
+

CallAbsolute

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64 (register)Uses scratch register for efficiency.
x86 (register)Uses scratch register for efficiency.
ARM64 (register)Uses scratch register (required)
+

CallRelative

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64+-2GiB
x86+-2GiB
ARM64+-128MiB
+

Return

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64
x86
ARM642 instructions if offset > 0.
+

Architecture Specific Operations

+

CallIpRelative

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64
x86Unsupported.
ARM64 (+- 1MiB)2 instructions.
ARM64 (+- 4GiB)3 instructions.
+

JumpIpRelative

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64
x86Unsupported.
ARM64 (+- 1MiB)2 instructions.
ARM64 (+- 4GiB)3 instructions.
+

Optimized Push/Pop Operations

+

MultiPush

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64*
x86*
ARM64Might fall back to single pop/push if mixing register sizes.
+

* Implemented but not used, due to more efficient code generation alternative.

+

MultiPop

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureSupportedNotes
x64*
x86*
ARM64Might fall back to single pop/push if mixing register sizes.
+

* Implemented but not used, due to more efficient code generation alternative.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/operations/index.html b/dev/arch/operations/index.html new file mode 100644 index 0000000..fbb43aa --- /dev/null +++ b/dev/arch/operations/index.html @@ -0,0 +1,1888 @@ + + + + + + + + + + + + + + + + + + + + + + + + Operations - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Operations

+
+

This page provides a reference for all of the various 'operations' implemented by individual JIT(s).

+
+
+

For more information about each of the operations, see the source code 😉 (enum Operation<T>).

+
+

Needed for Basic Hooking Support

+

JumpRelative

+
+

Represents jumping to a relative offset from current instruction pointer.

+
+
+
+
+
let jump_rel = JumpRelativeOperation {
+    target_address: 0x200,
+};
+
+
+
+
jmp 0x200 ; Jump to address at current IP + 0x200
+
+
+
+
b 0x200 ; Branch to address at current IP + 0x200
+
+
+
+
adrp x9, #0          ; Load 4K page, relative to PC. (round address down to 4096)
+add x9, x9, #100     ; Add any missing offset.
+blr x9               ; Branch to location
+
+
+
+
jmp 0x200 ; Jump to address at current IP + 0x200
+
+
+
+
+

JumpAbsolute

+
+

Represents jumping to an absolute address stored in a register.

+
+
+

JIT is free to encode this as a relative branch if it's possible.

+
+
+
+
+
let jump_abs = JumpAbsoluteOperation {
+    scratch_register: rax,
+    target_address: 0x123456,
+};
+
+
+
+
mov rax, 0x123456 ; Move target address into rax
+jmp rax ; Jump to address in rax
+
+
+
+
MOVZ x9, #0x3456        ; Set lower bits.
+MOVK x9, #0x12, LSL #16 ; Move upper bits
+br x9                   ; Branch to location
+
+
+
+
mov eax, 0x123456 ; Move target address into eax
+jmp eax ; Jump to address in eax
+
+
+
+
+
+

We prefer this approach to absolute jump because it is faster performance wise.

+
+

JumpAbsoluteIndirect

+
+

Represents jumping to an absolute address stored in a memory address.

+
+
+
+
+
let jump_ind = JumpIndirectOperation {
+    target_address: 0x123456,
+};
+
+
+
+
jmp qword [0x123456] ; Jump to address stored at 0x123456
+
+
+
+
jmp dword [0x123456] ; Jump to address stored at 0x123456
+
+
+
+
; Possible on Multiple of 0x10000 with offset 0-4096
+MOVZ x9, #0x123, LSL #16 ; Store upper 16 bits.
+LDR  x9, [x9, #0x456]    ; Load lower 12 bit offset
+br x9                    ; Branch to location
+
+
+
+
; On any address up to 4GiB + 4096
+MOVZ x9, #0x3456        ; Set lower bits.
+MOVK x9, #0x12, LSL #16 ; Move upper bits
+                        ; Continue until desired address.
+LDR  x9, [x9, #0x0]     ; Load from address.
+br x9
+
+
+
+
+
    +
  • Values in brackets indicate max address usable.
  • +
+
+

On MacOS, this is not usable, because memory < 2GiB is restricted from access.

+
+

Needed for Wrapper Generation

+
+

This includes functionality like 'parameter injection'.

+
+

Mov

+
+

Represents a move operation between two registers.

+
+
+
+
+
let move_op = MovOperation {
+    source: r8,
+    target: r9,  
+};
+
+
+
+
mov r9, r8 ; Move r8 into r9
+
+
+
+
mov x9, x8 ; Move x8 into x9
+
+
+
+
mov ebx, eax ; Move eax into ebx
+
+
+
+
+

MovFromStack

+
+

Represents a move operation from the stack into a register.

+
+
+
+
+
let move_from_stack = MovFromStackOperation {
+    stack_offset: 8,
+    target: rbx,
+};
+
+
+
+
mov rbx, [rsp + 8] ; Move value at rsp + 8 into rbx
+
+
+
+
ldr x9, [sp, #8] ; Load value at sp + 8 into x9
+
+
+
+
mov ebx, [esp + 8] ; Move value at esp + 8 into ebx
+
+
+
+
+

MovToStack

+
+

Represents moving a register value onto the stack at a user specified offset.

+
+
+
+
+
let mov_to_stack = MovToStackOperation {
+    register: rbx,
+    stack_offset: 16,  
+};
+
+
+
+
mov [rsp + 16], rbx ; Move rbx onto the stack 16 bytes above rsp 
+
+
+
+
str x9, [sp, #16] ; Store x9 onto the stack 16 bytes above sp
+
+
+
+
mov [esp + 16], ebx ; Move ebx onto the stack 16 bytes above esp
+
+
+
+
+

Push

+
+

Represents pushing a register onto the stack.

+
+
+
+
+
let push = PushOperation {
+    register: r9,
+};
+
+
+
+
push r9 ; Push rbx onto the stack
+
+
+
+
sub sp, sp, #8 ; Decrement stack pointer
+str x9, [sp] ; Store x9 on the stack
+
+
+
+
push ebx ; Push ebx onto the stack
+
+
+
+
+

PushStack

+
+

Represents pushing a value from the stack to the stack.

+
+
+
+
+
let push_stack = PushStackOperation {
+    offset: 8,
+    item_size: 8,
+};
+
+
+
+
push qword [rsp + 8] ; Push value at rsp + 8 onto the stack
+
+
+
+
ldr x9, [sp, #8] ; Load value at sp + 8 into x9
+sub sp, sp, #8 ; Decrement stack pointer
+str x9, [sp] ; Push x9 onto the stack
+
+
+
+
push [esp + 8] ; Push value at esp + 8 onto the stack
+
+
+
+
+

PushConstant

+
+

Represents pushing a constant value onto the stack.

+
+
+
+
+
let push_const = PushConstantOperation {
+    value: 10,
+};
+
+
+
+
push 10 ; Push constant value 10 onto stack
+
+
+
+
sub sp, sp, #8 ; Decrement stack pointer
+mov x9, 10 ; Move constant 10 into x9
+str x9, [sp] ; Store x9 on the stack
+
+
+
+
push 10 ; Push constant value 10 onto stack
+
+
+
+
+

StackAlloc

+
+

Represents adjusting the stack pointer.

+
+
+
+
+
let stack_alloc = StackAllocOperation {
+    operand: 8,
+};
+
+
+
+
sub rsp, 8 ; Decrement rsp by 8
+
+
+
+
sub sp, sp, #8 ; Decrement sp by 8
+
+
+
+
sub esp, 8 ; Decrement esp by 8
+
+
+
+
+

Pop

+
+

Represents popping a value from the stack into a register.

+
+
+
+
+
let pop = PopOperation {
+    register: rbx,
+};
+
+
+
+
pop rbx ; Pop value from stack into rbx
+
+
+
+
ldr x9, [sp] ; Load stack top into x9
+add sp, sp, #8 ; Increment stack pointer
+
+
+
+
pop ebx ; Pop value from stack into ebx
+
+
+
+
+

XChg

+
+

Represents exchanging the contents of two registers.

+
+
+

On some architectures (e.g. ARM64) this requires a scratch register.

+
+
+
+
+
let xchg = XChgOperation {
+    register1: r9,
+    register2: r8,
+    scratch: None,
+};
+
+
+
+
xchg r8, r9 ; Swap r8 and r9
+
+
+
+
// ARM doesn't have xchg instruction
+mov x10, x8 ; Move x8 into x10 (scratch register)
+mov x8, x9 ; Move x9 into x8
+mov x9, x10 ; Move original x8 (in x10) into x9
+
+
+
+
xchg eax, ebx ; Swap eax and ebx
+
+
+
+
+

CallAbsolute

+
+

Represents calling an absolute address stored in a register or memory.

+
+
+
+
+
let call_abs = CallAbsoluteOperation {
+    scratch_register: r9,
+    target_address: 0x123456,
+};
+
+
+
+
mov rax, 0x123456 ; Move target address into rax
+call r9 ; Call address in rax
+
+
+
+
adr x9, target_func ; Load address of target function into x9
+blr x9 ; Branch and link to address in x9
+
+
+
+
mov eax, 0x123456 ; Move target address into eax
+call eax ; Call address in eax
+
+
+
+
+

CallRelative

+
+

Represents calling a relative offset from current instruction pointer.

+
+
+
+
+
let call_rel = CallRelativeOperation {
+    target_address: 0x200,
+};
+
+
+
+
call 0x200 ; Call address at current IP + 0x200
+
+
+
+
bl 0x200 ; Branch with link to address at current IP + 0x200
+
+
+
+
call 0x200 ; Call address at current IP + 0x200
+
+
+
+
+

Return

+
+

Represents returning from a function call.

+
+
+
+
+
let ret = ReturnOperation {
+    offset: 4,
+};
+
+
+
+
ret ; Return
+ret 4 ; Return and add 4 to stack pointer
+
+
+
+
ret ; Return
+add sp, sp, #4 ; Add 4 to stack pointer
+ret ; Return
+
+
+
+
ret ; Return
+ret 4 ; Return and add 4 to stack pointer
+
+
+
+
+

Architecture Specific Operations

+
+

These operations are only available on certain architectures.

+
+
+

These are non essential, but can improve compatibility/performance.

+
+
+

Enabled by setting JitCapabilities::CanEncodeIPRelativeCall and JitCapabilities::CanEncodeIPRelativeJump in JIT.

+
+

CallIpRelative

+
+

Represents calling an IP-relative offset where target address is stored.

+
+
+
+
+
let call_rip_rel = CallIpRelativeOperation {
+    target_address: 0x1000,
+};
+
+
+
+
call qword [rip - 16] ; Address 0x1000 is at RIP-16 and contains raw address to call
+
+
+
+
ldr x9, 4 ; Read item in a multiple of 4 bytes relative to PC
+blr x9    ; Branch call to location
+
+
+
+
adrp x9, #0x0        ; Load 4K page, relative to PC. (round address down to 4096)
+ldr x9, [x9, 1110]   ; Read address from offset in 4K page.
+blr x9               ; Branch to location
+
+
+
+
+

JumpIpRelative

+
+

Represents jumping to an IP-relative offset where target address is stored.

+
+
+
+
+
let jump_rip_rel = JumpIpRelativeOperation {
+    target_address: 0x1000,
+};
+
+
+
+
jmp qword [rip - 16] ; Address 0x1000 is at RIP-16 and contains raw address to jump
+
+
+
+
ldr x9, 4 ; Read item in a multiple of 4 bytes relative to PC
+br x9     ; Branch call to location
+
+
+
+
adrp x9, #0x0        ; Load 4K page, relative to PC. (round address down to 4096)
+ldr x9, [x9, 1110]   ; Read address from offset in 4K page.
+br x9                ; Branch call to location
+
+
+
+
+

Optimized Push/Pop Operations

+
+

Enabled by setting JitCapabilities::CanMultiPush in JIT.

+
+

MultiPush

+
+

Represents pushing multiple registers onto the stack.

+
+
+

Implementations must support push/pop of mixed registers (e.g. Reg+Vector).

+
+
+
+
+
let multi_push = MultiPushOperation {
+    registers: [
+        PushOperation { register: rbx },
+        PushOperation { register: rax },
+        PushOperation { register: rcx },
+        PushOperation { register: rdx },
+    ],
+};
+
+
+
+
push rbx
+push rax
+push rcx
+push rdx ; Push rbx, rax, rcx, rdx onto the stack
+
+
+
+
sub sp, sp, #32 ; Decrement stack pointer by 32 bytes  
+stp x9, x8, [sp] ; Store x9 and x8 on the stack
+stp x11, x10, [sp, #16] ; Store x11 and x10 on the stack  
+
+
+
+
push ebx
+push eax
+push ecx
+push edx ; Push ebx, eax, ecx, edx onto the stack
+
+
+
+
+

MultiPop

+
+

Represents popping multiple registers from the stack.

+
+
+

Implementations must support push/pop of mixed registers (e.g. Reg+Vector).

+
+
+
+
+
let multi_pop = MultiPopOperation {
+    registers: [
+        PopOperation { register: rdx },
+        PopOperation { register: rcx },
+        PopOperation { register: rax },
+        PopOperation { register: rbx },
+    ],
+};
+
+
+
+
pop rdx
+pop rcx
+pop rax
+pop rbx ; Pop rdx, rcx, rax, rbx from the stack
+
+
+
+
ldp x11, x10, [sp], #16 ; Load x11 and x10 from stack and update stack pointer
+ldp x9, x8, [sp], #16 ; Load x9 and x8 from stack and update stack pointer
+
+
+
+
pop edx
+pop ecx
+pop eax
+pop ebx ; Pop edx, ecx, eax, ebx from the stack
+
+
+
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/overview/index.html b/dev/arch/overview/index.html new file mode 100644 index 0000000..351c51e --- /dev/null +++ b/dev/arch/overview/index.html @@ -0,0 +1,1371 @@ + + + + + + + + + + + + + + + + + + + + + + + + Overview - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Architecture Overview

+

Lists currently supported architectures and their features.

+

Feature Support

+
+

Lists the currently available library features for different architectures.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Featurex86 & x64ARM64
Basic Function Hooking
Code Relocation✅*
Hook Stacking
Calling Convention Wrapper Generation
Optimal Wrapper Generation
Length Disassembler
+
    +
  • x86 should work in all cases, but x64 isn't tested against all 5000+ instructions.
  • +
+

Required

+

Basic Function Hooking

+
+

The ability to hook/detour existing application functions.

+
+

How to Implement

+
+

Implement a code writer by inheriting the Jit<TRegister> trait

+
+

In the writer, implement at least the following operations:

+ +

Your Platform must also support Permission Change, if it is +applicable to your platform.

+

Length Disassembler

+
+

Length disassembly is the ability to determine instruction lengths at a given address.

+
+

A length disassembler determines the minimum amount of instructions (in bytes) needed to copy when hooking +a function.

+
/// Disassembles items at `code_address` until the length of instructions
+/// is equal to or greater than `min_length`. 
+/// 
+/// # Returns
+/// Returns length of instructions (in bytes) greater than or equal to min_length
+fn disassemble_length(code_address: usize, min_length: usize) -> usize
+
+

This is done by disassembling the original instructions at code_address, incrementing a length for each +encountered instruction until length >= min_length, then returning the result.

+

Example

+

For hooking functions, it's necessary to inject a jmp instruction into the existing code.

+

For example, given this sequence:

+
; x86 Assembly
+DoMathWithTwoNumbers:
+    cmp rcx, 0 ; 48 83 F9 00
+    jg skipAdd ; 7F 0E
+
+    mov rax, [rsp + 8] ; 48 8B 44 24 04
+    mov rax, [rsp + 16] ; 48 8B 4C 24 04
+    add rax, rcx ; 48 01 C8
+    ret ; C3
+
+

A `5 byte`` relative jump would overwrite the first two instructions, creating:

+
; x86 Assembly
+DoMathWithTwoNumbers:
+    jmp stub ; E9 XX XX XX XX
+    <INVALID INSTRUCTION> ; 0E
+
+    mov rax, [rsp + 8] ; 48 8B 44 24 04
+    mov rax, [rsp + 16] ; 48 8B 4C 24 04
+    add rax, rcx ; 48 01 C8
+    ret ; C3
+
+

When calling the original function again, and thus creating the Reverse Wrapper, +the original instructions overwritten by the jmp will need to be executed.

+

To do this, we must know that the original 2 instructions at DoMathWithTwoNumbers were 6, NOT +5 bytes in length total. Such that when we copy the original code to Reverse Wrapper +we get

+
cmp rcx, 0 ; 48 83 F9 00
+jg skipAdd ; 7F 0E
+
+

and not

+
cmp rcx, 0 ; 48 83 F9 00
+<INVALID INSTRUCTION> ; 7F
+
+

With a length disassembler, we are able to safely copy all the bytes needed.

+

How to Implement

+
+

Implement a length disassembler by inheriting the LengthDisassembler trait.

+
+

Use the algorithm described in example.

+

Code Relocation

+
+

Code relocation is the ability to rewrite existing code such that existing instructions using PC/IP relative operands still have valid operands post patching.

+
+

Suppose the following x86 code, which was optimised away to accept first parameter in ecx register:

+
int DoMathWithTwoNumbers(int operation@ecx, int a, int b) {
+
+    if (operation <= 0) {
+        return a + b;
+    }
+
+    // Omitted Code Here
+}
+
+

In this case it's possible that there's a jump in the very beginning of the function:

+
DoMathWithTwoNumbers:
+    cmp ecx, 0
+    jg skipAdd # It's greater than 0
+
+    mov eax, [esp + {wordSize * 1}] ; Left Parameter
+    mov ecx, [esp + {wordSize * 2}] ; Right Parameter
+    add eax, ecx
+    ret
+
+    ; Some Omitted Code Here
+
+skipAdd:
+    ; Omitted Code Here
+
+

In a scenario like this, the hooking library would overwrite the cmp and jg instruction when +it assembles the hook entry ('enter hook'); and when the original +function is called again by your hook the, 'wrapper' would now contain this jg instruction.

+

Because jg is an instruction relative to the current instruction address, the library must be able +to patch and 'relocate' the function to a new address.

+
+

Basic code relocation support is needed to stack hooks.

+
+

How to Implement

+
+

Implement a relocator by CodeRewriter trait.

+
+

There is no 'general strategy' for this, however, here are some pieces of advice:

+
    +
  • Consider looking at the docs for existing relocators (for RISC, ARM64) is a good reference.
  • +
  • You will need to rewrite all control flow instructions (branch etc.)
  • +
  • You will need to rewrite all instructions which are relative to current Instruction Pointer/Program Counter.
  • +
  • Use disassembler library (if one exists) for your architecture.
  • +
+

Optional (Extras)

+

Calling Convention Wrapper Generation

+
+

The ability to convert between different calling conventions (e.g. cdecl -> stdcall).

+
+

To implement this, you implement a code writer by inheriting the Jit<TRegister> trait; and +implement the following operations:

+ +

Optimized Wrapper Generation

+
+

If this is checked, it means the wrappers generate optimal code (to best of knowledge).

+
+
+

While the wrapper generator does most optimisations themselves, in some cases, it may be possible to perform additional optimisations in the JIT/Code Writer side.

+
+

For example, the reloaded-hooks wrapper generator might generate the following sequence of pushes for ARM64:

+
push x0
+push x1
+
+

A clever ARM64 compiler however would be able to translate this to:

+
stp x0, x1, [sp, #-16]!
+
+

For some built in optimisations, like this, you can opt into these specialised instructions with JitCapabilities on your Jit<TRegister>.

+

Some others, may be implemented at Jit level instead.

+

Misc

+

Hook Stacking

+
+

Hook stacking is the ability to hook a function multiple times.

+
+

This should work flawlessly out of the box if all of the required elements are implemented.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/x86/code_relocation/index.html b/dev/arch/x86/code_relocation/index.html new file mode 100644 index 0000000..3f4794b --- /dev/null +++ b/dev/arch/x86/code_relocation/index.html @@ -0,0 +1,1388 @@ + + + + + + + + + + + + + + + + + + + + + + + + Code Relocation - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Code Relocation

+
+

This page provides a listing of all instructions rewritten as part of the Code Relocation process for x86 architecture.

+
+

This page provides a comprehensive overview of the instruction rewriting techniques used in the code +relocation process, specifically tailored for the x64 architecture.

+

Any Instruction within 2GiB Range

+
+

If the new relative branch target is within the encodable range, it is left as relative.

+
+

Example: Within Relative Range

+

Original: (EB 02)
+- jmp +2

+

Relocated: (E9 FF 0F 00 00)
+- jmp +4098

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+`#[case::simple_branch("eb02", 4096, 0, "e9ff0f0000")]
+
+
+

In x86, any address is reachable from any address

+

This is due to integer over/underflow and immediates being 2GiB in size. Therefore relocation +simply involves extending the immediate as needed, i.e. jmp 0x12 to jmp 0x123012 etc.

+

The rest of the page will therefore leave out relative cases, and only focus on offsets greater +than 2GiB.

+
+

x64 Rewriter: Going Beyond the 2GiB Offset

+
+

The x64 rewriter is only suitable for rewriting function prologues.

+
+

To be able to perform a lot of actions in a position independent manner, this rewriter uses a dummy +'scratch' register which it will overwrite.

+

Scratch register is determined by the following logic:

+
    +
  • Start with Caller Saved Registers (these restored after function call).
  • +
  • Remove all registers used in code being rewritten.
  • +
+

Because rewriting a lot of code will lead to register exhaustion, it must be reiterated the rewriter can only be used for small bits of code.

+
+

x64 has over 5000 ‼️ instructions that require rewriting. Only a couple hundred are tested currently

+
+

Relative Branches

+

Instructions such as JMP, CALL, etc.

+

Behaviour:

+

If out of range, it is rewritten using a combination of MOV (move the absolute address into a register) followed by JMP or CALL to that register.

+

Example

+

Original: (EB 02)
+- jmp +2

+

Relocated: (48 B8 04 00 00 80 00 00 00 00 FF E0)
+- mov rax, 0x80000004
+- jmp rax

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+#[case::to_abs_jmp_i8("eb02", 0x80000000, 0, "48b80400008000000000ffe0")]
+
+

Jump Conditional

+

Instructions such as jne, jg etc.

+

Behaviour:

+
    +
  • Inverts the branch condition, then jumps over an absolute jump that is encoded using a MOV to set the address and a JMP to that address.
  • +
+

Example

+

Example:

+

Original: (70 02)
+- jo +2

+

Relocated: (71 0C 48 B8 04 00 00 80 00 00 00 FF E0):
+- jno +12 <skip>
+- mov rax, 0x80000004
+- jmp rax

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+#[case::jo("7002", 0x80000000, 0, "710c48b80400008000000000ffe0")]
+
+

Loop Instructions

+

Instructions such as LOOP, LOOPE, and LOOPNE.

+

Behaviour:

+

Handled by either:

+
    +
  • Manually decrementing ECX and using a conditional jump based on the zero flag. (i.e. extend 'loop' address to 32-bit)
  • +
+

or

+
    +
  • Branching the loop function in the opposite direction.
  • +
+

The strategy used depends on the original instruction.

+

Example: Branch in Opposite Direction

+

Original: (E2 FA)
+- loop -3

+

Relocated: (50 E2 02 EB 0C 48 B8 FD 0F 00 80 00 00 00 00 FF E0)
+- push rax
+- loop +2
+- jmp 0x11
+- movabs rax, 0x80000ffd
+- jmp rax

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+#[case::loop_backward_abs("50e2fa", 0x80001000, 0, "50e202eb0c48b8fd0f008000000000ffe0")]
+
+

JCX Instructions

+

Instructions such as JCXZ, JECXZ, JRCXZ.

+

Behaviour:

+
    +
  • If the target is within 32-bit range, it uses an optimized IMM32 encoding.
  • +
  • If out of 32-bit range, it uses a TEST instruction followed by a conditional jump.
  • +
+

Example

+

Original: (E3 FA)
+- jrcxz -3

+

Relocated: (E3 02 EB 0C 48 B8 FD 0F 00 80 00 00 00 00 FF E0)
+- jrcxz +5
+- jmp 0x11
+- mov rax, 0x80000ffd
+- jmp rax

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+#[case::jrcxz_abs("e3fa", 0x80001000, 0, "e302eb0c48b8fd0f008000000000ffe0")]
+
+

RIP Relative Operand

+
+

At time of writing, this covers around 2800 ‼️ instructions

+
+
+

Only around a 100 are covered by unit tests though.

+
+

Covers all instructions which have an IP relative operand, i.e. read/write to a memory address +which is relative to the address of the next instruction.

+

Behaviour:

+

Replace RIP relative operand with a scratch register with the originally intended memory address.

+

Example

+

Original: (48 8B 1D 08 00 00 00)
+- mov rbx, [rip + 8]

+

Relocated: (48 B8 0F 00 00 00 01 00 00 00 48 8B 18)
+- mov rax, 0x10000000f
+- mov rbx, [rax]

+
// Parameters for test case:
+// - Original Code (Hex)
+// - Original Address
+// - New Address
+// - New Expected Code (Hex)
+#[case::mov_rhs("488b1d08000000", 0x100000000, 0, "48b80f00000001000000488b18")]
+
+

How this is Done

+

reloaded-hooks-rs uses the iced library under the hood for +assembly and disassembly.

+

In iced, operands can be broken down to 3 main types:

+ + + + + + + + + + + + + + + + + + + + + +
NameNote
registerIncluding Vector Registers
memoryi.e. [rax] or [rip + 4]
immImmediate, 8/16/32/64
+
+

Immediates use multiple types, e.g. Immediate8, Immediate16 etc. but on assembler side you can pass them all as Immediate32, so you can group them.

+
+

Each instruction can have 0-5 operands, where there is at max 1 operand which can be RIP relative.

+

To handle this, a script projects/code-generators/x86/generate_enum_ins_combos.py was used to dump +all possible operand permutations from Iced source. Then I wrote functions to handle each possible permutation.

+

1 Operand:

+
    +
  • rip
  • +
+

2 Operands:

+
    +
  • rip, imm
  • +
  • rip, reg
  • +
  • reg, rip
  • +
+

3 Operands:

+
    +
  • reg, reg, rip
  • +
  • reg, rip, imm
  • +
  • rip, reg, imm
  • +
  • rip, reg, reg
  • +
  • reg, rip, reg
  • +
+

4 Operands:

+
    +
  • reg, reg, rip, imm
  • +
  • reg, reg, reg, rip
  • +
+

5 Operands:

+
    +
  • reg, reg, reg, rip, imm
  • +
  • reg, reg, rip, reg, imm
  • +
+

If reloaded-hooks-rs encounters an instruction with RIP relative operand that uses any of the +following operand permutations, it should successfully patch it.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/x86/x86/index.html b/dev/arch/x86/x86/index.html new file mode 100644 index 0000000..a6a7867 --- /dev/null +++ b/dev/arch/x86/x86/index.html @@ -0,0 +1,1077 @@ + + + + + + + + + + + + + + + + + + + + + + + + Overview (x86) - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

x86

+
+

This is just a quick reference sheet for developers.

+
+
    +
  • Code Alignment: 16 bytes
  • +
+

Registers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Registerstdcall (Microsoft x86)cdecl
eaxCaller-saved, return valueCaller-saved, return value
ebxCallee-savedCallee-saved
ecxCaller-savedCaller-saved
edxCaller-savedCaller-saved
esiCallee-savedCallee-saved
ediCallee-savedCallee-saved
ebpCallee-savedCallee-saved
espCallee-savedCallee-saved
+

For floating point registers:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Registerstdcall (Microsoft x86)cdecl
st(0)-st(7)Caller-saved, st(0) used for returning floating point values.Caller-saved, st(0) used for returning floating point values.
mm0-mm7Caller-savedCaller-saved
xmm0-xmm7Caller-savedCaller-saved
+

Both calling conventions pass function parameters on the stack, in right-to-left order, and they +both return values in eax. For floating-point values or larger structures, the FPU stack or +additional conventions are used. The main difference for function calls is that stdcall expects +the function (callee) to clean up the stack, while cdecl expects the caller to do it.

+

Calling Convention Inference

+
+

It is recommended library users manually specify conventions in their hook functions."

+
+

When the calling convention of <your function> is not specified, wrapper libraries must insert +the appropriate default convention in their wrappers.

+

Rust

+
    +
  • i686-pc-windows-gnu: cdecl
  • +
  • i686-pc-windows-msvc: cdecl
  • +
  • i686-unknown-linux-gnu: SystemV
  • +
+

C

+
    +
  • Linux x86: SystemV
  • +
  • Windows x86: cdecl
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/arch/x86/x86_64/index.html b/dev/arch/x86/x86_64/index.html new file mode 100644 index 0000000..1262496 --- /dev/null +++ b/dev/arch/x86/x86_64/index.html @@ -0,0 +1,1221 @@ + + + + + + + + + + + + + + + + + + + + + + + + Overview (x86_64) - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

x86_64

+
+

This is just a quick reference sheet for developers.

+
+
    +
  • Code Alignment: 16 bytes
  • +
+

Registers

+

The order of the registers is typically as follows for Microsoft x64 ABI: rcx, rdx, r8, r9, +then the rest of the parameters are pushed onto the stack in reverse order (right-to-left).

+

For the System V ABI on x64: rdi, rsi, rdx, rcx, r8, r9, then the rest of the parameters +are pushed onto the stack in reverse order (right-to-left).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RegisterMicrosoft x64 ABISystemV ABI
raxCaller-savedCaller-saved
rbxCallee-savedCallee-saved
rcxCaller-saved, 1st parameterCaller-saved, 4th parameter
rdxCaller-saved, 2nd parameterCaller-saved, 3rd parameter
rsiCaller-savedCaller-saved, 2nd parameter
rdiCaller-savedCaller-saved, 1st parameter
rbpCallee-savedCallee-saved
rspCallee-savedCallee-saved
r8Caller-saved, 3rd parameterCaller-saved, 5th parameter
r9Caller-saved, 4th parameterCaller-saved, 6th parameter
r10Caller-savedCaller-saved
r11Caller-savedCaller-saved
r12Callee-savedCallee-saved
r13Callee-savedCallee-saved
r14Callee-savedCallee-saved
r15Callee-savedCallee-saved
+
+

Floating Point Registers (Microsoft)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RegisterMicrosoft x64 ABI
st(0)-st(7)Caller-saved
mm0-mm7Caller-saved
xmm0-xmm5Caller-saved, used for floating point parameters.
ymm0-zmm5Caller-saved, used for floating point parameters.
zmm0-zmm5Caller-saved, used for floating point parameters.
xmm6-xmm15Callee-saved.
ymm6-ymm15Callee-saved. Upper half must be preserved by the caller
zmm6-zmm31Callee-saved. Upper half must be preserved by the caller
+
+

Floating Point Registers (SystemV)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RegisterSystemV ABI
st(0)-st(7)Caller-saved
mm0-mm7Caller-saved
xmm0-xmm7Caller-saved, used for floating point parameters
ymm0-zmm7Caller-saved, used for floating point parameters
zmm0-zmm7Caller-saved, used for floating point parameters
xmm8-xmm15Caller-saved
ymm8-ymm15Caller-saved, used for floating point parameters
zmm8-zmm31Caller-saved, used for floating point parameters
+
+

On Linux, syscalls use R10 instead of RCX in SystemV ABI

+
+

Intel APX

+
+

Information sourced from Source.

+
+

Future Intel processors are expected to ship with APX, extending the registers to 32 by adding R16-R31.

+

These future registers are expected to be caller saved.

+

To quote document:

+
+

Defining all new state (Intel® APX’s EGPRs) as volatile (caller-saved or scratch)

+
+

Calling Convention Inference

+
+

It is recommended library users manually specify conventions in their hook functions."

+
+

When the calling convention of <your function> is not specified, wrapper libraries must insert +the appropriate default convention in their wrappers.

+

Rust

+
    +
  • x86_64-pc-windows-gnu: Microsoft
  • +
  • x86_64-pc-windows-msvc: Microsoft
  • +
  • x86_64-unknown-linux-gnu: SystemV
  • +
  • x86_64-apple-darwin: SystemV
  • +
+

C

+
    +
  • Windows x64: Microsoft
  • +
  • Linux x64: SystemV
  • +
  • macOS x64: SystemV
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/assembly-hooks/overview/index.html b/dev/design/assembly-hooks/overview/index.html new file mode 100644 index 0000000..5dbcd3a --- /dev/null +++ b/dev/design/assembly-hooks/overview/index.html @@ -0,0 +1,1245 @@ + + + + + + + + + + + + + + + + + + + + + + + + Assembly Hooks - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Assembly Hooks

+
+

Replacing arbitrary assembly sequences (a.k.a. 'mid function hooks').

+
+
+

This hook is used to make small changes to existing logic, for example injecting custom logic for existing conditional branches (if statements).

+
+
+

Limited effectiveness if Code Relocation is not available.

+
+
+

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

+
+

This hook works by injecting a jmp instruction inside the middle of an arbitrary assembly sequence +to custom code. The person using this hook must be very careful not to break the program +(corrupt stack, used registers, etc.).

+

High Level Diagram

+

Key

+
    +
  • Original Code: Middle of an arbitrary sequence of assembly instructions where a branch to custom code is placed.
  • +
  • Hook Function: Contains user code, including original code (depending on user preference).
      +
    • When the hook is deactivated, this contains the original code only.
    • +
    +
  • +
  • Original Stub: Original code (used when hook disabled).
  • +
+

When Activated

+
flowchart TD
+    O[Original Code]
+    HK[Hook Function]
+
+    O -- jump --> HK
+    HK -- jump back --> O
+

When the hook is activated, a branch is placed in the middle of the original assembly instruction +sequence to your hook code.

+

Your code (and/or original code) is then executed, then it branches back to original code.

+

When Deactivated

+
flowchart TD
+    O[Original Function]
+    HK["Hook Function &lt;Overwritten with Original Code&gt;"]
+
+    O -- jump --> HK
+    HK -- jump back --> O
+

When the hook is deactivated, the 'Hook Function' is overwritten in-place with original instructions +and a jump back to your code.

+

Usage Notes

+
+

Assembly Hooks should allow both Position Independent Code and Position Relative Code

+
+

With that in mind, the following APIs should be possible:

+
/// Creates an Assembly Hook given existing position independent assembly code,
+/// and address which to hook.
+/// # Arguments
+/// * `hook_address` - The address of the function or mid-function to hook.
+/// * `asm_code` - The assembly code to execute, precompiled.
+fn from_pos_independent_code_and_function_address(hook_address: usize, asm_code: &[u8]);
+
+/// Creates an Assembly Hook given existing position assembly code,
+/// and address which to hook.
+/// 
+/// # Arguments
+/// * `hook_address` - The address of the function or mid-function to hook.
+/// * `asm_code` - The assembly code to execute, precompiled.
+/// * `code_address` - The original address of asm_code. 
+/// 
+/// # Remarks
+/// Code in `asm_code` will be relocated to new target address. 
+fn from_code_and_function_address(hook_address: usize, asm_code: &[u8], code_address: usize);
+
+/// Creates an Assembly Hook given existing position assembly code,
+/// and address which to hook.
+/// 
+/// # Arguments
+/// * `hook_address` - The address of the function or mid-function to hook.
+/// * `asm_isns` - The assembly instructions to place at this address.
+/// 
+/// # Remarks
+/// Code in `asm_code` will be relocated to new target address. 
+fn from_instructions_and_function_address(hook_address: usize, asm_isns: &[Instructions]);
+
+
+

Using overloads for clarity, in library all options should live in a struct.

+
+

Code using from_code_and_function_address is to be preferred for usage, as users will be able to use +relative branches for improved efficiency. (If they are out of range, hooking library will rewrite them)

+

For pure assembly code, users are expected to compile code externally using something like FASM, +put the code in their program/mod (as byte array) and pass that directly as asm_code.

+

For people who want to call their own program/mod(s) from assembly, there will be a wrapper API around +Jit<TRegister> and its various Operations. This API will be cross-architecture and +should contain all the necessary operations required for setting up stack/registers and calling user code.

+

Programmers are also expected to provide 'max allowed hook length' with each call.

+

Hook Lengths

+
+

The expected hook lengths for each architecture

+
+

When using the library, the library will use the most optimal possible jmp instruction to get to the user hook.

+

When calling one of the functions to create an assembly hook, the end user should specify their max permissible assembly hook length.

+

If a hook cannot be satisfied within that constraint, then library will throw an error.

+

The following table below shows common hook lengths, for:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArchitectureRelativeTMAWorst Case
x86[1]5 bytes (+- 2GiB)5 bytes5 bytes
x86_645 bytes (+- 2GiB)6 bytes[2]13 bytes[3]
x86_64 (macOS)5 bytes (+- 2GiB)13 bytes[4]13 bytes[3]
ARM644 bytes (+- 128MiB)12 bytes[6]20 bytes[5]
ARM64 (macOS)4 bytes (+- 128MiB)12 bytes[6]20 bytes[5]
+

[1]: x86 can reach any address from any address with relative branch due to integer overflow/wraparound.
+[2]: jmp [<Address>], with <Address> at < 2GiB.
+[3]: mov <reg>, address + call <reg>. +1 if using an extended reg.
+[4]: macOS restricts access to < 2GiB memory locations, so absolute jump must be used. +1 if using an extended reg.
+[5]: MOVZ + MOVK + LDR + BR.
+[6]: ADRP + ADD + BR.

+

Thread Safety, Memory Layout & State Switching

+ +

Legacy Compatibility Considerations

+
+

As reloaded-hooks-rs intends to replace Reloaded.Hooks is must provide certain functionality for backwards compatibility.

+
+
+

Once reloaded-hooks-rs releases, the legacy Reloaded.Hooks will be a wrapper around it.

+
+

This means a few functionalities must be supported here:

+
    +
  • +

    Setting arbitrary 'Hook Length'.

    +
      +
    • This is the amount of bytes stolen from the original code to be included as 'original code' in hook.
    • +
    • On x86 Reloaded.Hooks users create an ASM Hook (with default PreferRelativeJump == false and HookLength == -1) the wrapper for legacy API must set 'Hook Length' == 7 to emulate absolute jump size.
    • +
    • Respecting MaxOpcodeSize from original API should be sufficient.
    • +
    +
  • +
  • +

    Supporting Assembly via FASM.

    +
      +
    • As this is only possible in Windows (FASM can't be recompiled on other OSes as library), this feature will be getting dropped.
    • +
    • The Reloaded.Hooks wrapper will continue to ship FASM for backwards compatibility, however mods are expected to migrate to the new library in the future.
    • +
    +
  • +
+

Limits

+

Assembly hook info is packed by default to save on memory space. By default, the following limits apply:

+ + + + + + + + + + + + + + + + + + + + +
Property4 Byte Instruction (e.g. ARM64)Other (e.g. x86)
Max Orig Code Length128KiB32KiB
Max Hook Code Length128KiB32KiB
+
+

These limits may increase in the future if additional functionality warrants extending metadata length.

+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/branch-hooks/overview/index.html b/dev/design/branch-hooks/overview/index.html new file mode 100644 index 0000000..c27aead --- /dev/null +++ b/dev/design/branch-hooks/overview/index.html @@ -0,0 +1,1362 @@ + + + + + + + + + + + + + + + + + + + + + + + + Branch Hooks - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Branch Hooks

+
+

Replaces a branch (call/jump) to an existing method with a new one.

+
+
+

This hook is commonly used when you want to change behaviour of a function, but only for certain callers.

+

For example, if you have a method Draw2DElement that's used to draw an object to the screen, but +you only want to move a certain element that's rendered by Draw2DElement, you would use a Branch Hook +to replace call Draw2DElement to call YourOwn2DElement.

+
+
+

Only guaranteed to work on platforms with Targeted Memory Allocation

+

Because the library needs to be able to acquire memory in proximity of the original function.

+

Usually this is almost always achievable, but cases where Denuvo DRM inflates ARM64 binaries +(20MB -> 500MB) may prove problematic as ARM64 has +-128MiB range for relative jumps.

+
+
+

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

+
+

This hook works by replacing the target of a call (a.k.a. Branch with Link) instruction with a new target.

+

Comparison with Function Hook

+
+

A Branch Hook is really a specialised variant of function hook.

+
+

Notably it differs in the following ways:

+
    +
  • +

    There is no Wrapper To Call Original Function as no instructions are stolen.

    +
      +
    • Your method will directly call original instead.
    • +
    +
  • +
  • +

    You call the ReverseWrapper instead of jumping to it.

    +
  • +
  • Code replacement is at caller level rather than function level.
  • +
+

High Level Diagram

+

Key

+
    +
  • Caller Function: Function which originally called Original Method.
  • +
  • ReverseWrapper: Translates from original function calling convention to yours. Then calls your function.
  • +
  • <Your Function>: Your Rust/C#/C++/Asm code.
  • +
  • Original Method: Original method to be called.
  • +
+

When Activated

+
flowchart TD
+    CF[Caller Function]
+    RW[Stub]
+    HK["&lt;Your Function&gt;"]
+    OM[Original Method]
+
+    CF -- "call wrapper" --> RW
+    RW -- jump to your code --> HK
+    HK -. "Calls &lt;Optionally&gt;" .-> OM
+    OM -. "Returns" .-> HK
+

When Activated in 'Fast Mode'

+
+

'Fast Mode' is an optimisation that inserts the jmp to point directly into your code when possible.

+
+
flowchart TD
+    CF[Caller Function]
+    HK["&lt;Your Function&gt;"]
+    OM[Original Method]
+
+    CF -- "call 'Your Function' instead of original" --> HK
+    HK -. "Calls &lt;Optionally&gt;" .-> OM
+    OM -. "Returns" .-> HK
+

This option allows for a small performance improvement, saving 1 instruction and some instruction prefetching load.

+

This is on by default (can be disabled), and will take into effect when no conversion between calling conventions is needed.

+

When Activated (with Calling Convention Conversion)

+
flowchart TD
+    CF[Caller Function]
+    RW[ReverseWrapper]
+    HK["&lt;Your Function&gt;"]
+    W[Wrapper]
+    OM[Original Method]
+
+    CF -- "call wrapper" --> RW
+    RW -- jump to your code --> HK
+    HK -. "Calls &lt;Optionally&gt;" .-> W
+    W -- "call original (wrapped)" --> OM
+    OM -. "Returns" .-> W
+    W -. "Returns" .-> HK
+

When Deactivated

+
flowchart TD
+    CF[Caller Function]
+    SB[Stub]
+    HK[Hook Function]
+    OM[Original Method]
+
+    CF -- jump to stub --> SB
+    SB -- jump to original --> OM
+

When the hook is deactivated, the stub is replaced with a direct jump back to the original function.

+

By bypassing your code entirely, it is safe for your dynamic library (.dll/.so/.dylib) +to unload from the process.

+

Thread Safety, Memory Layout & State Switching

+ +

Stub Memory Layout

+

The 'branch hook' stub uses the following memory layout:

+
- [Branch to Hook Function / Branch to Original Function]
+- Branch to Hook Function
+- Branch to Original Function
+
+

If calling convention conversion is needed, the layout looks like this:

+
- [ReverseWrapper / Branch to Original Function]
+- ReverseWrapper
+- Branch to Original Function
+- Wrapper
+
+
+

The library is optimised to not use redundant memory

+

For example, in x86 (32-bit), a jmp instruction can reach any address from any address. In that situation, +we don't write Branch to Original Function to the buffer at all, provided a ReverseWrapper is not needed, +as it is not necessary.

+
+

Examples

+
+

Using x86 Assembly.

+
+
Before
+
originalCaller:
+    ; Some code...
+    call originalFunction
+    ; More code...
+
+
After (Fast Mode)
+
originalCaller:
+    ; Some code...
+    call userFunction ; To user method
+    ; More code...
+
+userFunction:
+    ; New function implementation...
+    call originalFunction ; Optional.
+
+
After
+
; x86 Assembly
+originalCaller:
+    ; Some code...
+    call stub
+    ; More code...
+
+stub:
+    ; == BranchToHook ==
+    jmp newFunction
+    ; == BranchToHook ==
+
+    ; == BranchToOriginal ==
+    jmp originalFunction
+    ; == BranchToOriginal ==
+
+newFunction:
+    ; New function implementation...
+    call originalFunction ; Optional.
+
+
After (with Calling Convention Conversion)
+
; x86 Assembly
+originalCaller:
+    ; Some code...
+    call stub
+    ; More code...
+
+stub:
+    ; == ReverseWrapper ==
+    ; implementation..
+    call userFunction
+    ; ..implementation
+    ; == ReverseWrapper ==
+
+    ; == Wrapper ==
+    ; implementation ..
+    jmp originalFunction
+    ; .. implementation
+    ; == Wrapper ==
+
+    ; == BranchToOriginal ==
+    jmp originalFunction ; Whenever disabled :wink:
+    ; == BranchToOriginal ==
+
+userFunction:
+    ; New function implementation...
+    call wrapper; (See Above)
+
+
After (Disabled)
+
; x86 Assembly
+originalCaller:
+    ; Some code...
+    call stub
+    ; More code...
+
+stub:
+    <jmp to `jmp originalFunction`> ; We disable the hook by branching to instruction that branches to original
+    jmp originalFunction ; Whenever disabled :wink:
+
+newFunction:
+    ; New function implementation...
+    call originalFunction ; Optional.
+
+originalFunction:
+    ; Original function implementation...
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/common/index.html b/dev/design/common/index.html new file mode 100644 index 0000000..345b16c --- /dev/null +++ b/dev/design/common/index.html @@ -0,0 +1,1794 @@ + + + + + + + + + + + + + + + + + + + + + + + + Common Notes - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Common Design Notes

+
+

Design notes common to all hooking strategies.

+
+

Wrappers

+

Wrapper

+
+

Wrappers are stubs which convert from the calling convention of the original function to your calling convention.

+
+
+

If the calling convention of the hooked function and your function matches, this wrapper is simply just 1 jmp instruction.

+
+

Wrappers are documented in their own page here.

+

ReverseWrapper

+
+

Stub which converts from your code's calling convention to original function's calling convention

+
+
+

This is basically Wrapper with source and destination swapped around

+
+

Hook Memory Layouts & Thread Safety

+
+

Hooks in reloaded-hooks-rs are structured in a very specific way to ensure thread safety.

+
+
+

They sacrifice a bit of memory usage in favour of performance + thread safety.

+
+

Most hooks, regardless of type have a memory layout that looks something like this:

+
// Size: 2 registers
+pub struct Hook
+{
+    /// The address of the stub containing bridging code
+    /// between your code and custom code. This is the address
+    /// of the code that will actually be executed at runtime.
+    stub_address: usize,
+
+    /// Address of the 'properties' structure, containing
+    /// the necessary info to manipulate the data at stub_address
+    props: NonNull<StubPackedProps>,
+}
+
+

Notably, there are two heap allocations. One at stub_address, which contains the executable code, +and one at props, which contains packed info of the stub at stub_address.

+

The hooks use a 'swapping' system. Both stub_address and props contains swap space. When you +enable or disable a hook, the data in the two 'swap spaces' are swapped around.

+

In other words, when stub_address' 'swap space' contains the code for HookFunction (hook enabled), +the 'swap space' at props' contains the code for Original Code.

+

Thread safety is ensured by making writes within the stub itself atomic, as well as making the emplacing +of the jump to the stub in the original application code atomic.

+

Stub Layout

+
+

The memory region containing the actual executed code.

+
+

The stub has two possible layouts, if the Swap Space is small enough such that it can be atomically +overwritten, it will look like this:

+
- 'Swap Space' [HookCode / OriginalCode]
+<pad to atomic register size>
+
+

Otherwise, if Swap Space cannot be atomically overwritten, it will look like:

+
- 'Swap Space' [HookCode / OriginalCode]
+- HookCode
+- OriginalCode
+
+
+

Some hooks may store, extra data after OriginalCode.

+
+

For example, if calling convention conversion is needed, the HookCode becomes a +ReverseWrapper, and the stub will also contain a Wrapper.

+

If calling convention conversion is needed, the layout looks like this:

+
- 'Swap Space' [ReverseWrapper / OriginalCode]
+- ReverseWrapper
+- OriginalCode
+- Wrapper
+
+

Example

+
+

Using ARM64 Assembly Hook as an example.

+
+

If the 'OriginalCode' was:

+
mov x0, x1
+add x0, x2
+
+

And the 'HookCode' was:

+
add x1, x1
+mov x0, x2
+
+

The memory would look like this when hook is enabled.

+
swap: ; Currently Applied (Hook)
+    mov x0, x1
+    add x0, x2
+    b back_to_code
+
+hook: ; HookCode
+    add x1, x1
+    mov x0, x2
+    b back_to_code
+
+original: ; OriginalCode
+    mov x0, x1
+    add x0, x2
+    b back_to_code
+
+

(When sizeof(swap) is larger than biggest possible atomic write.)

+

Heap (Props) Layout

+

Each Assembly Hook contains a pointer to the heap stub (seen above) and a pointer to the heap.

+

The heap contains all information required to perform operations on the stub.

+
- StubPackedProps
+    - Enabled Flag
+    - IsSwapOnly
+    - SwapSize
+    - HookSize
+- [Hook Function / Original Code]
+
+

The data in the heap contains a short `StubPackedProps`` struct, detailing the data stored over in the +stub.

+

The SwapSize contains the length of the 'swap' info (and also consequently, offset of HookCode).
+The HookSize contains the length of the 'hook' instructions (and consequently, offset of OriginalCode).

+

If the IsSwapOnly flag is set, then this data is to be atomically overwritten.

+

The 'Enable' / 'Disable' Process

+
+

When transitioning between Enabled/Disabled state, we place a temporary branch at entry, this allows us to manipulate the remaining code safely.

+
+
+

Using ARM64 Assembly Hook as an example.

+
+

We start the 'disable' process with a temporary branch:

+
entry: ; Currently Applied (Hook)
+    b original ; Temp branch to original
+    mov x0, x2
+    b back_to_code
+
+hook: ; Backup (Hook)
+    add x1, x1
+    mov x0, x2
+    b back_to_code
+
+original: ; Backup (Original)
+    mov x0, x1
+    add x0, x2
+    b back_to_code
+
+
+

Don't forget to clear instruction cache on non-x86 architectures which need it.

+
+

This ensures we can safely overwrite the remaining code...

+

Then we overwrite entry code with hook code, except the branch:

+
entry: ; Currently Applied (Hook)
+    b original     ; Branch to original
+    add x0, x2     ; overwritten with 'original' code.
+    b back_to_code ; overwritten with 'original' code.
+
+hook: ; Backup (Hook)
+    add x1, x1
+    mov x0, x2
+    b back_to_code
+
+original: ; Backup (Original)
+    mov x0, x1
+    add x0, x2
+    b back_to_code
+
+

And lastly, overwrite the branch.

+

To do this, read the original sizeof(nint) bytes at entry, replace branch bytes with original bytes +and do an atomic write. This way, the remaining instruction is safely replaced.

+
entry: ; Currently Applied (Hook)
+    add x1, x1     ; 'original' code.
+    add x0, x2     ; 'original' code.
+    b back_to_code ; 'original' code.
+
+original: ; Backup (Original)
+    mov x0, x1
+    add x0, x2
+    b back_to_code
+
+hook: ; Backup (Hook)
+    add x1, x1
+    mov x0, x2
+    b back_to_code
+
+

This way we achieve zero overhead CPU-wise, at expense of some memory.

+

Limits

+

Stub info is packed by default to save on memory space. By default, the following limits apply:

+ + + + + + + + + + + + + + + + + + + + +
Property4 Byte Instruction (e.g. ARM64)Other (e.g. x86)
Max Orig Code Length128KiB32KiB
Max Hook Code Length128KiB32KiB
+
+

These limits may increase in the future if additional required functionality warrants extending metadata length.

+
+

Thread Safety on x86

+
+

Thread safety is 'theoretically' not guaranteed for every possible x86 processor, however is satisfied for all modern CPUs.

+
+
+

The information below is x86 specific but applies to all architectures with a non-fixed instruction size. Architectures with fixed instruction sizes (e.g. ARM) are thread safe in this library by default.

+
+

The Theory

+
+

If the jmp instruction emplaced when switching state overwrites what originally + were multiple instructions, it is theoretically possible that the placing the jmp will make the + instruction about to be executed invalid.

+
+

For example if the previous instruction sequence was:

+
0x0: push ebp
+0x1: mov ebp, esp ; 2 bytes
+
+

And inserting a jmp produces:

+
0x0: jmp disabled ; 2 bytes
+
+

It's possible that the CPU's Instruction Pointer was at 0x1`` at the time of the overwrite, making themov ebp, esp` instruction invalid.

+

What Happens in Practice

+

In practice, modern x86 CPUs (1990 onwards) from Intel, AMD and VIA prefetch instruction in batches +of 16 bytes. We place our stubs generated by the various hooks on 16-byte boundaries for this +(and optimisation) reasons.

+

So, by the time we change the code, the CPU has already prefetched the instructions we are atomically +overwriting.

+

In other words, it is simply not possible to perfectly time a write such that a thread at 0x1 +(mov ebp, esp) would read an invalid instruction, as that instruction was prefetched and is being +executed from local thread cache.

+

What is Safe

+

Here is a thread safety table for x86, taking the above into account:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Safe?HookNotes
FunctionFunctions start on multiples of 16 on pretty much all compilers, per Intel Optimisation Guide.
BranchStubs are 16 aligned.
AssemblyStubs are 16 aligned.
VTableVTable entries are usize aligned, and don't cross cache boundaries.
+

Hook Length Mismatch Problem

+
+

When a hook is already present, and you wish to stack that hook over the existing hook, certain problems might arise.

+
+

When your hook is shorter than original.

+
+

This is notably an issue when a hook entry composes of more than 1 instruction; i.e. on RISC architectures.

+
+
+

There is a potential register allocation caveat in this scenario.

+
+

Pretend you have the following ARM64 function:

+
+
+
+
ADD x1, #5
+ADD x2, #10
+ADD x0, x1, x2
+ADD x0, x0, x0
+RET
+
+
+
+
x1 = x1 + 5;
+x2 = x2 + 10;
+int x0 = x1 + x2;
+x0 = x0 + x0;
+return x0;
+
+
+
+
+

And then, a large hook using an absolute jump with register is applied:

+
# Original instructions here replaced
+MOVZ x0, A
+MOVK x0, B, LSL #16
+MOVK x0, C, LSL #32
+MOVK x0, D, LSL #48
+B x0
+# <= branch returns here
+
+

If you then try to apply a smaller hook after applying the large hook, you might run into the following situation:

+
# The 3 instructions here are an absolute jump using pointer.
+adrp x9, [0]        
+ldr x9, [x9, 0x200] 
+br x9
+# Call to original function returns here, back to then branch to previous hook
+MOVK x0, D, LSL #48
+B x0
+
+

This is problematic, with respect to register allocation. +Absolute jumps on some RISC platforms like ARM will always require the use of a scratch register.

+

But there is a risk the scratch register used is the same register (x0) as the register used by the +previous hook as the scratch register. In which case, the jump target becomes invalid.

+

Resolution Strategy

+
    +
  • Prefer absolute jumps without scratch registers (if possible).
  • +
  • Detect mov + branch combinations for each target architecture.
      +
    • And extend the function's stolen bytes to cover the entirety.
    • +
    • This avoids the scratch register duplication issue, as original hook code will branch to its own +code before we end up using the same scratch register.
    • +
    +
  • +
+

When your hook is longer than original.

+
+

Only applies to architectures with variable length instructions. (x86)

+
+
+

Some hooking libraries don't clean up remaining stolen bytes after installing a hook.

+
+
+

Very notably Steam does this for rendering (overlay) and input (controller support).

+
+

Consider the original function having the following instructions:

+
48 8B C4      mov rax, rsp
+48 89 58 08   mov [rax + 08], rbx
+
+

After Steam hooks, it will leave the function like this

+
E9 XX XX XX XX    jmp 'somewhere'
+58 08             <invalid instruction. leftover from state before>
+
+

If you're not able to install a relative hook, e.g. need to use an absolute jump

+
FF 25 XX XX XX XX    jmp ['addr']
+
+

The invalid instructions will now become part of the 'stolen' bytes, when you call the original; +and invalid instructions may be executed.

+

Resolution Strategy

+

This library must do the following:

+
    +
  • Prefer shorter hooks (relative jump over absolute jump) when possible.
  • +
  • Leave nop(s) after placing any branches, to avoid leaving invalid instructions.
      +
    • Don't contribute to the problem.
    • +
    +
  • +
+

There unfortunately isn't much we can do to detect invalid instructions generated by other hooking libraries +reliably, best we can do is try to avoid it by using shorter hooks. Thankfully this is not a common issue +given most people use the 'popular' libraries.

+

Fallback Strategies

+

Return Address Patching

+
+

This feature will not be ported over from legacy Reloaded.Hooks, until an edge case is found that requires this.

+
+
+

This section explains how Reloaded handles an edge case within an already super rare case.

+
+
+

This topic is a bit more complex, so we will use x86 as example here.

+
+

For any of this to be necessary, the following conditions must be true:

+
    +
  • An existing relative jump hook exists.
  • +
  • Reloaded can't find free memory within relative jump range.
  • +
  • The existing hook was somehow able to find free memory in this range, but we can't... (<= main reason this is improbable!!)
  • +
  • Free Space from Function Alignment Strategy fails.
  • +
  • The instructions at beginning of the hooked function happened to just perfectly align such that our hook + jump is longer than the existing one.
  • +
+

The low probability of this happening, at least on Windows and/or Linux is rather insane. It cannot +be estimated, but if I were to have a guess, maybe 1 in 1 billion. You'd be more likely to die +from a shark attack.

+
+

In any case, when this happens, Reloaded performs return address patching.

+

Suppose a foreign hooking library hooks a function with the following prologue:

+
55        push ebp
+89 e5     mov ebp, esp
+00 00     add [eax], al
+83 ec 20  sub esp, 32 
+...
+
+

After hooking, this code would look like:

+
E9 XX XX XX XX  jmp 'somewhere'
+<= existing hook jumps back here when calling original (this) function
+83 ec 20        sub esp, 32 
+...
+
+

When the prologue is set up 'just right', such that the existing instrucions divide perfectly +into 5 bytes, and we need to insert a 6 byte absolute jmp FF 25, Reloaded must patch the return address.

+

Reloaded has a built in patcher for this super rare scenario, which detects and attempts to patch return +addresses of the following patterns:

+
Where nop* represents 0 or more nops.
+
+1. Relative immediate jumps.       
+
+    nop*
+    jmp 0x123456
+    nop*
+
+2. Push + Return
+
+    nop*
+    push 0x612403
+    ret
+    nop*
+
+3. RIP Relative Addressing (X64)
+
+    nop*
+    JMP [RIP+0]
+    nop*
+
+

This patching mechanism is rather complicated, relies on disassembling code at runtime and thus won't be explained here.

+
+

Different hooking libraries use different logic for storing callbacks. In some cases alignment of code (or rather lack thereof) can also make this operation unreliable, since we rely on disassembling the code at runtime to find jumps back to end of hook. The success rate of this operation is NOT 100%

+
+

Requirements for External Libraries to Interoperate

+
+

While I haven't studied the source code of other hooking libraries before, I've had no issues in the past with the common Detours and minhook libraries that are commonly used

+
+

Hooking Over Reloaded Hooks

+
+

Libraries which can safely interoperate (stack hooks ontop) of Reloaded Hooks Hooks' must satisfy the following.

+
+
    +
  • +

    Must be able to patch (re-adjust) relative jumps.

    +
      +
    • In some cases when assembling call to original function, relative jump target may be out of range, + compatible hooking software must handle this edge case.
    • +
    +
  • +
  • +

    Must be able to automatically determine number of bytes to steal from original function.

    +
      +
    • This makes it possible to interoperate with the rare times we do a absolute jump when + it may not be possible to do a relative jump (i.e.) as we cannot allocate memory in close + enough proximity.
    • +
    +
  • +
+

Reloaded Hooks hooking over Existing Hooks

+ + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/function-hooks/hooking-strategy-arm64/index.html b/dev/design/function-hooks/hooking-strategy-arm64/index.html new file mode 100644 index 0000000..55522bd --- /dev/null +++ b/dev/design/function-hooks/hooking-strategy-arm64/index.html @@ -0,0 +1,932 @@ + + + + + + + + + + + + + + + + + + + + + + + + ARM64 - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Interoperability (ARM64)

+
+

Please read the general section first, this contains ARM64 specific stuff.

+
+

Fallback Strategy: Free Space from Function Alignment

+ +

In the case of ARM64, padding is usually down with the following sequences:
+- nop (0xD503201F, big endian), used by GCC.
+- and x0, x0 (0x00000000), used by MSVC.

+
+

Getting sufficient bytes to make good use of them in ARM64 is more uncommon than x86.

+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/function-hooks/hooking-strategy-x86/index.html b/dev/design/function-hooks/hooking-strategy-x86/index.html new file mode 100644 index 0000000..ff58376 --- /dev/null +++ b/dev/design/function-hooks/hooking-strategy-x86/index.html @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + x86 - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Interoperability (x86)

+
+

Please read the general section first, this contains x86 specific stuff.

+
+

Fallback Strategy: Free Space from Function Alignment

+ +
    +
  • x86 programs align instructions on 16 byte boundaries.
  • +
  • Bytes 0x90 (GCC) or 0xCC (MSVC) are commonly used for padding.
  • +
+

Fallback Strategy: Return Address Patching

+ +

We use x86 in the example for general section above.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/function-hooks/hooking-strategy/index.html b/dev/design/function-hooks/hooking-strategy/index.html new file mode 100644 index 0000000..10dd85b --- /dev/null +++ b/dev/design/function-hooks/hooking-strategy/index.html @@ -0,0 +1,1111 @@ + + + + + + + + + + + + + + + + + + + + + + + + General - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Interoperability (General)

+
+

This page just contains common information regarding interoperability that are common to all platforms.

+
+

Interpoerability in this sense means 'stacking hooks ontop of other libraries', and how other libraries +can stack hooks ontop of reloaded-hooks-rs.

+

General Hooking Strategy

+
+

This is the general hooking strategy employed by reloaded-hooks; derived from the facts in the rest of this document.

+
+

To ensure maximum compatibility with existing hooking systems, reloaded-hooks uses +relative jumps as these are the most popular, +and thus best supported by other libraries when it comes to hook stacking.

+

These are the lowest overhead jumps, so are preferable in any case.

+

If Relative Jump is Not Possible

+

In the very, very, unlikely event that using (target is further than +max relative jump distance), the following strategy below is used.

+

No Existing Hook

+

If no existing hook exists, an absolute jump will be used (if possible).
+- Prefer indirect absolute jump (if possible).

+
+

We check for presence of 'existing hook' by catching some common instruction patterns.

+
+

Existing Hook

+ +

Calling Back into Original Function

+

In order to optimize the code relocation process, reloaded-hooks, +will try to find a buffer that's within relative jump range to the original jump target.

+

If this is not possible, reloaded-hooks will start rewriting relative jump(s) +from the original function to absolute jump(s) in the presence +of recognised patterns; if the code rewriter supports this.

+

Fallback Strategies

+
+

Strategies used for improving interoperability with other hooks.

+
+

Free Space from Function Alignment

+
+

This is a strategy for encoding absolute jumps using fewer instructions.

+
+
+

Processors typically fetch instructions 16 byte boundaries.

+
+
+

To optimise for this, compilers pad the space between end of last function and start of next.

+
+
+

We can exploit this 😉

+
+

If there's sufficient padding before the function, we can: +- Insert our absolute jump there, and branch to it.
+or +- Insert jump target there, and branch using that jump target.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/function-hooks/overview/index.html b/dev/design/function-hooks/overview/index.html new file mode 100644 index 0000000..bc5036f --- /dev/null +++ b/dev/design/function-hooks/overview/index.html @@ -0,0 +1,1187 @@ + + + + + + + + + + + + + + + + + + + + + + + + Overview - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Function Hooks

+
+

How hooking around entire functions works.

+
+
+

This hook is used to run custom callback for a function, modify its parameters or replace a function entirely. It is the most common hook.

+
+
+

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

+
+

This hook works by injecting a jmp instruction at the beginning of a function to a custom +replacement function, or a stub which will later call that function.

+

When the original function is called, it is done via a wrapper, which restores the originally +overwritten instructions that were sacrificed for the jmp.

+

High Level Diagram

+

Key

+
    +
  • Stolen Bytes: Bytes used by instructions sacrificed in original function to place a 'jmp' to the ReverseWrapper.
  • +
  • ReverseWrapper: Translates from original function calling convention to yours. Then calls your function.
  • +
  • <Your Function>: Your Rust/C#/C++/Asm code.
  • +
  • Wrapper: Translates from your calling convention to original, then runs the original function.
  • +
+

When Activated

+
flowchart TD
+    orig[Original Function] -- jump to wrapper --> rev[Reverse Wrapper]
+    rev -- jump to your code --> target["&lt;Your Function&gt;"]
+    target -- "call original via wrapper" --> stub["Wrapper &lt;with stolen bytes + jmp to original&gt;"]
+    stub -- "call original" --> original["Original Function"]
+
+    original -- "return value" --> stub
+    stub -- "return value" --> target
+

When the hook is activated, a stub calls into your function; which becomes the 'new original function'; +that is, control will return (ret) to the original function's caller from this function.

+

When your function calls the original function, it will be an entirely separate method call.

+
+

Your function can technically not call the original and replace it outright.

+
+

When Activated in 'Fast Mode'

+
+

'Fast Mode' is an optimisation that inserts the jmp to point directly into your code when possible.

+
+
flowchart TD
+    orig[Original Function] -- to your code --> target["&lt;Your Function&gt;"]
+    target -- "call original via wrapper" --> stub["Wrapper &lt;with stolen bytes + jmp to original&gt;"]
+    stub -- "call original" --> original["Original Function"]
+
+    original -- "return value" --> stub
+    stub -- "return value" --> target
+

This option allows for a small performance improvement, saving 1 instruction and some instruction prefetching load.

+

This is on by default (can be disabled), and will take into effect when no conversion between calling conventions is needed.

+

When conversion is needed, the logic will default back to When Activated.

+
+

When 'Fast Mode' is enabled, you lose the ability to unhook (for compatibility reasons).

+
+

When Deactivated

+
+

Does not apply to 'Fast Mode'. When in fast mode, deactivation returns error.

+
+
flowchart TD
+    orig[Original Function] -- jump to wrapper --> stub["Stub &lt;stolen bytes + jmp&gt;"]
+    stub -- "jmp original" --> original["Original Function"]
+

When you deactivate a hook, the contents of 'Reverse Wrapper' are overwritten with the stolen bytes.

+
+

When 'Reverse Wrapper' is allocated, extra space is reserved for original code.

+
+

By bypassing your code entirely, it is safe for your dynamic library (.dll/.so/.dylib) +to unload from the process.

+

Calling Convention Inference

+
+

It is recommended library users manually specify conventions in their hook functions."

+
+

When the calling convention of <your function> is not specified, wrapper libraries must insert +the appropriate default convention in their wrappers.

+
+

On Linux, syscalls use R10 instead of RCX in SystemV ABI

+
+

Rust

+
    +
  • i686-pc-windows-gnu: cdecl
  • +
  • i686-pc-windows-msvc: cdecl
  • +
  • +

    i686-unknown-linux-gnu: SystemV (x86)

    +
  • +
  • +

    x86_64-pc-windows-gnu: Microsoft x64

    +
  • +
  • x86_64-pc-windows-msvc: Microsoft x64
  • +
  • x86_64-unknown-linux-gnu: SystemV (x64)
  • +
  • x86_64-apple-darwin: SystemV (x64)
  • +
+

C

+
    +
  • Windows x86: cdecl
  • +
  • +

    Windows x64: Microsoft x64

    +
  • +
  • +

    Linux x64: SystemV (x64)

    +
  • +
  • +

    Linux x86: SystemV (x86)

    +
  • +
  • +

    macOS x64: SystemV (x64)

    +
  • +
+

Wrapper(s)

+
+

Wrappers are stubs which convert from the calling convention of the original function to your calling convention.

+
+
+

If the calling convention of the hooked function and your function matches, this wrapper is simply just 1 jmp instruction.

+
+

Wrappers are documented in their own page here.

+

ReverseWrapper(s)

+
+

Stub which converts from your code's calling convention to original function's calling convention

+
+
+

This is basically Wrapper with source and destination swapped around

+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/vtable-hooks/overview/index.html b/dev/design/vtable-hooks/overview/index.html new file mode 100644 index 0000000..d58617d --- /dev/null +++ b/dev/design/vtable-hooks/overview/index.html @@ -0,0 +1,1082 @@ + + + + + + + + + + + + + + + + + + + + + + + + VTable Hooks - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

VTable Hooks

+
+

Replaces a pointer inside an array of function pointers with a new pointer.

+
+
+

This hook is commonly used to hook COM objects, e.g. Direct3D.

+
+
+

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

+
+

Probably the simplest hook out of them all, it's simply replacing one pointer inside an array of function +pointers with a new one.

+

About VTables

+
+

VTables, are what is used to support polymorphism in C++ and similar languages.

+
+

They are the mechanism that enables calling correct functions in presence of inheritance and virtual functions.

+

Basically what drives 'interfaces' in other languages.

+

VTables in MSVC & GCC

+

In both GCC and Visual C++, VTables are automatically created for classes that have virtual functions.

+

They are located at offset 0x0 of any class, thus if you get a pointer to a class, and dereference offset +0x0, you'll be at the address of the first item in the VTable.

+
+
+
+
class Item {
+    virtual void doSomething();
+    int k;
+};
+
+
+
+
class Item
+    void* vTable
+    int k
+
+
vTable:
+    void* doSomething
+
+
+
+
+

VTables exist in .rdata, thus you need to change memory permissions when hooking them.

+

VTables in COM Objects

+

One notable thing about COM is that all interfaces inherit from IUnknown, +so the first 4 methods will always be the 4 methods of IUnknown.

+

High Level Diagram

+
+

Using Direct3D9 as an example

+
+

Before

+
flowchart LR
+    EndScene --> EndScene_Orig 
+    Clear --> Clear_Orig
+    SetTransform --> SetTransform_Orig
+    GetTransform  --> GetTransform_Orig
+

After

+
flowchart LR
+    EndScene --> EndScene_Hook --> Your_Function --> EndScene_Orig
+    Clear --> Clear_Orig
+    SetTransform --> SetTransform_Orig
+    GetTransform  --> GetTransform_Orig
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/design/wrappers/index.html b/dev/design/wrappers/index.html new file mode 100644 index 0000000..7aaad2d --- /dev/null +++ b/dev/design/wrappers/index.html @@ -0,0 +1,1829 @@ + + + + + + + + + + + + + + + + + + + + + + + + Wrapper Generation - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Calling Conversion Wrappers

+
+

Describes how stubs for converting between different Calling Conventions (ABIs) are generated.

+
+
+

This page uses x86 as an example, however the same concepts apply to other architectures.

+
+

These stubs are what allows Reloaded.Hooks-rs to hook functions which take parameters in custom registers, +allowing developers to skip writing error prone 'naked' functions by hand.

+

General Strategy

+
+

Setting frame pointer (ebp) is not necessary, as our wrapper shouldn't use it

+
+
    +
  • Backup Non-Volatile Registers (incl. Link Register on Relevant Platforms)
  • +
+
# push LR if present on platform
+push ebp
+push ebx
+push edi
+push esi
+
+
    +
  • Align the Stack
  • +
  • +

    Setup Function Parameters

    +
      +
    • Re push stack parameters (right to left) of the function being returned
    • +
    +
    # In a loop
    +push dword [ebp + {baseStackOffset}]
    +
    +
      +
    • Push register parameters of the function being returned (right to left, reverse loop)
    • +
    • Pop parameters into registers of function being called
    • +
    +
  • +
  • +

    Reserve Extra Stack Space

    +
  • +
+

Some calling conventions require extra space reserved up front

+
sub esp, {whatever}
+
+
    +
  • Call Target Method
  • +
  • Setup Return Register
  • +
+

If target function returns in different register than caller expects, might need to for example mov eax, ecx.

+
mov eax, ecx
+
+
    +
  • Restore Non-Volatile Registers
  • +
+
# Restore non-volatile registers
+pop esi
+pop edi
+pop ebx
+pop ebp
+# pop LR if relevant on given platform
+
+
+

The general implementation for 64-bit is the same, however the stack must be 16 byte aligned at method entry, and for MSFT convention, 32 bytes reserved on stack before call

+
+

There are also some very minor nuances, which the actual code has to handle, but this is the general +jist of it.

+

Optimization

+ +
+

This optimizes CPU instruction fetch, which (on x86) operates on 16 byte boundaries.

+
+

So we align our wrappers to these boundaries.

+

Eliminate Callee Saved Registers

+
+

When there are overlaps in callee saved registers between source and target, we can skip backing up those registers.

+
+

For example, cdecl and stdcall use the same callee saved registers, ebp, ebx, esi, edi. When converting between these two conventions, it is not necessary to backup/restore any of them in the wrapper, because the target function will already take care of that.

+

Example: cdecl target -> stdcall wrapper.

+
+
+
+
# Stack Backup
+push ebp
+mov ebp, esp
+
+# Callee Save
+push ebx
+push edi
+push esi
+
+# Re push parameters
+push dword [ebp + {x}]
+push dword [ebp + {x}]
+
+call {function}
+add esp, 8
+
+# Callee Restore
+pop esi
+pop edi
+pop ebx
+
+# Stack Restore
+pop ebp
+ret 8
+
+
+
+
# Stack Backup
+push ebp
+mov ebp, esp
+
+# Re push parameters
+push dword [ebp + {x}]
+push dword [ebp + {x}]
+
+call {function}
+add esp, 8
+
+# Stack Restore
+pop ebp
+ret 8
+
+
+
+
+
+

Pseudocode example. Not verified for accuracy, but it shows the idea

+
+

In the cdecl -> stdcall example, ebp is considered a callee saved register too, thus it should be possible to optimise into:

+
# Re push parameters
+push dword [esp + {x}]
+push dword [esp + {x}]
+
+call {function}
+add esp, 8
+
+ret 8
+
+

Combine Push/Pop Operations when Possible

+
+

When pushing multiple registers at once, it is possible to remove redundant stack operations.

+
+

Imagine a situation where you need to push 3 float registers onto the stack; if we pass the instructions +from the wrapper generator verbatim [push a, then push b, then push c], we would land with the following:

+
; Push XMM registers
+sub rsp, 16
+movdqu [rsp], xmm0
+sub rsp, 16
+movdqu [rsp], xmm1
+sub rsp, 16
+movdqu [rsp], xmm2
+
+; Pop XMM registers
+movdqu xmm2, [rsp]
+add rsp, 16
+movdqu xmm1, [rsp]
+add rsp, 16
+movdqu xmm0, [rsp]
+add rsp, 16
+
+

This is unoptimal as it can be simplified to:

+
# Push Registers to the Stack
+sub rsp, 48
+movdqu [rsp], xmm0 
+movdqu [rsp + 16], xmm1
+movdqu [rsp + 32], xmm2
+
+# Pop three XMM registers from the Stack
+movdqu xmm0, [rsp]
+movdqu xmm1, [rsp + 16]
+movdqu xmm2, [rsp + 32]
+add rsp, 48
+
+

When generating wrappers, the generator must recognise this pattern, and merge multiple +push/pop operations into a single block, wherever possible.

+
+

It is optimal to access memory sequentially from lowest to highest address.

+
+

Move Between Registers Instead of Push Pop

+
+

In some cases it's possible to mov between registers, rather than doing an explicit push+pop operation

+
+

Suppose you have a custom target -> stdcall wrapper. Custom is defined as int@eax FastAdd(int a@eax, int b@ecx).

+

Normally wrapper generation will convert the arguments like this:

+
# Re-push STDCALL arguments to stack
+push dword [esp + {x}]
+push dword [esp + {x}]
+
+# Pop into correct registers
+pop eax
+pop ecx
+
+# Call that function
+# ...
+
+

There's opportunities for optimisation here; notably you can do:

+
# Pop into correct registers
+mov eax, [esp + {x}]
+mov ecx, [esp + {x}]
+
+# Call that function
+# ...
+
+

Optimising cases where the source/from convention, e.g. custom target -> stdcall has no register +parameters is trivial, since you can directly mov into the intended target register. And this is +the most common use case in x86.

+

For completeness, it should be noted that in the opposite direction stdcall target -> custom, such +as one that would be used in entry point of a hook (ReverseWrapper), +no optimisation is needed here, as all registers are directly pushed without any extra steps.

+
+

In the backend, the wrapper generator keeps track of current stack pointer (assuming start is '0'); and uses that information to match the push and pop operations accordingly 😉

+
+

With Register to Register

+
+

In x64, and more advanced x86 scenarios where both to/from calling convention have register parameters, mov optimisation is not trivial.

+
+
Basic Case
+

Suppose you have a a function to add 'health' to a character that's in a struct or class. i.e. int AddHealth(Player* this, int amount). +(Note: The 'this' parameter to struct instance is implicit and added during compilation.)

+
+
+
+
class Player {
+    int mana;
+    int health;
+
+    void AddHealth(int amount) {
+        health += amount;
+    }
+};
+
+
+
+
add dword [rdi+4], esi
+ret
+
+

See for yourself.

+
+
+
add dword [rcx+4], edx
+ret
+
+

See for yourself.

+
+
+
+

If you were to make a SystemV target -> Microsoft wrapper; you would have to move the two registers +from rcx, rdx to rdi, rsi.

+

Therefore, a wrapper might have code that looks something like:

+
# Push register parameters of the function being returned (right to left, reverse loop)
+push rdx
+push rcx
+
+# Pop parameters into registers of function being called
+pop rdi
+pop rsi
+
+

In this case, it is possible to optimise with:

+
mov rdi, rcx # last push, first pop
+mov rsi, rdx # second last push, second pop
+
+

Provided that the wrapper correctly saves and restores callee moved registers for returned method, i.e. +backs up RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15, this is fine.

+

Or in the case of this wrapper, just RDI, RSI (due to overlap within the 2 conventions).

+
+

The 'strategy' to generate code for this optimisation is keeping track of stack, start between push and pop in the ASM and pair the registers in the corresponding push and pop operations together, going outwards until there is no push/pop left.

+
+
Advanced Case
+
+

This is just another example.

+
+

Suppose we add 2 more parameters...

+
+
+
+
class Player {
+    int mana;
+    int health;
+    int money;
+
+    void AddStats(int health, int mana, int money) {
+        this->health += health;
+        this->mana += mana;
+        this->money += money;
+    }
+};
+
+
+
+
add dword [rdi+4], esi # health
+add dword [rdi], edx   # mana
+add dword [rdi+8], ecx # money
+ret
+
+

See for yourself.

+
+
+
add dword [rcx+4], edx # health
+add dword [rcx], r8d   # mana
+add dword [rcx+8], r9d # money
+ret
+
+

See for yourself.

+
+
+
+

There is now an overlap between the registers used.

+

Microsoft convention uses:
+- rcx for self
+- rdx for health

+

SystemV uses:
+- rcx for money
+- rdx for mana

+

The wrapper now does the following

+
+
+
+
# Push register parameters of the function being returned (right to left, reverse loop)
+push rcx
+push rdx
+push rsi
+push rdi
+
+# Pop parameters into registers of function being called
+pop rcx
+pop rdx
+pop r8
+pop r9
+
+
+
+
mov rcx, rdi
+mov rdx, rsi
+mov r8, rdx
+mov r9, rcx
+
+
+
+
+
+

The optimised version of code above contains a bug.

+
+

There is a bug because both conventions have overlapping registers, notably rcx and rdx. When +you try to do mov r8, rdx, this pushes invalid data, as rdx was already overwritten.

+

In this specific case, you can reverse the order of operations, and get a correct result:

+
# Reversed
+mov r9, rcx
+mov r8, rdx
+mov rdx, rsi
+mov rcx, rdi
+
+

However might not always be the case.

+

When generating wrappers, we must perform a validation check to determine if any source register +in mov target, source hasn't already been overwritten by a prior operation.

+
Reordering Operations
+
+

In the Advanced Case we saw that it's not always possible to perform mov optimisation.

+
+
+

This problem can be solved with a Directed Acyclic Graph.

+
+

This problem can be solved in O(n) complexity with a Directed Acyclic Graph, where each node represents +a register and an edge (arrow) from Node A to Node B represents a move from register A to register B.

+

The above (buggy) code would be represented as:
+

flowchart TD
+    RDI --> RCX
+    RSI --> RDX
+    RDX --> R8
+    RCX --> R9

+

RDI writes to RCX which writes to R9, which is now invalid.
+We can determine the correct mov order, by processing them in reverse order of their dependencies

+
    +
  • mov r9, rcx before mov rcx, rdi
  • +
  • mov r8, rdx before mov rdx, rsi
  • +
+
+

Exact order encoded depends on algorithm implementation in code; as long as the 2 derived rules are followed.

+
+
Handling Cycles (2 Node Cycle)
+

Suppose we have 2 calling conventions with reverse parameter order. For this example we will define +convention 🐱call. 🐱call uses the reverse register order of Microsoft compiler.

+
+
+
+
int AddWithShift(int a, int b) {
+    return (a * 16) + b;
+}
+
+
+
+
shl ecx, 4
+lea eax, dword [rdx+rcx]
+ret
+
+
+
+
shl edx, 4
+lea eax, dword [rcx+rdx]
+ret
+
+
+
+
+

The ASM to do the calling convention transformation becomes:

+
+
+
+
# Push register parameters of the function being returned (right to left, reverse loop)
+push rcx
+push rdx
+
+# Pop parameters into registers of function being called
+pop rcx
+pop rdx
+
+
+
+
mov rcx, rdx
+mov rdx, rcx
+
+
+
+
+

There is now a cycle.

+
flowchart TD
+    RCX --> RDX
+    RDX --> RCX
+

In this trivial example, you can use xchg or 3 mov(s) to swap between the two registers.

+
+
+
+
xchg rcx, rdx
+
+
+
+
mov {temp}, rdx
+mov rdx, rcx
+xor rcx, {temp}
+
+
+
+
+

On some Intel architectures, the mov approach can reportedly be faster, however, it's not possible +to procure a scratch register in all cases.

+
+

I'll welcome any PRs that detect and write the more optimal choice on a given architecture, however this is not planned for main library.

+
+

Adding instructions also means the wrapper might overflow to the next multiple of 16 bytes, causing +more instructions to be fetched when it otherwise won't happend with xchg, potentially losing any +benefits gained on those architectures.

+
+

The mappings done in Reloaded.Hooks are a 1:1 bijective mapping. Therefore any cycle of just 2 registers can be resolved by simply swapping the involved registers.

+
+
Handling Cycles (Multi Register Cycle)
+

Now imagine doing a mapping which involves 3 registers, r8 - r10, and all registers need to be mov'd.

+
flowchart TD
+    R8 --> R9
+    R9 --> R10
+    R10 --> R8
+
mov R9, R8
+mov R10, R9
+mov R8, R10
+
+

To resolve this, we backup the register at the end of the cycle (in this case R10), disconnect it +from the first register in the cycle and resolve as normal.

+

i.e. we solve for

+
flowchart TD
+    R8 --> R9
+    R9 --> R10
+

Then write original value of R10 into R8 after this code is converted into mov sequences.

+

This can be done using the following strategies:

+
    +
  • mov into scratch register.
      +
    • For mid-function hooks (AsmHook) prefer callee saved register which is not a parameter.
    • +
    • For function hooks, use a caller saved register.
    • +
    +
  • +
  • push + pop register.
  • +
+
+
+
+
# Move value from end of cycle into caller saved register (scratch)
+mov RAX, R10
+
+# Original (after reorder)
+mov R10, R9
+mov R9, R8
+
+# Move from caller saved register into first in cycle.
+mov R8, RAX
+
+
+
+
# Push value from end of cycle into stack
+push R10
+
+# Original (after reorder)
+mov R10, R9
+mov R9, R8
+
+# Pop into intended place from stack
+pop R8
+
+
+
+
+

When possible to get scratch register, use mov, otherwise use push.

+

Idea: Eliminate Return Address

+
+

This is a theoretical idea, not implemented in library.

+
+
+

Only applies to platforms like x86 return addresses on stack.

+
+

In some cases, like converting between stdcall and cdecl; it might be possible to reuse the same +parameters from the stack. Take into account the previous example:

+
# Re push parameters
+push dword [esp + {x}]
+push dword [esp + {x}]
+
+call {function}
+add esp, 8
+
+ret 8
+
+

Strictly speaking, to convert from stdcall to cdecl, you will only need to convert from +caller stack cleanup to callee stack cleanup i.e. ret 8.

+

In this case, re-pushing parameters is redundant, as the pushed parameters from the previous +method call are on stack and can still be re-used.

+

What we can instead do, is overwrite the return address and jump to our code.

+
# Pop previous return address from stack
+mov [esp], {addressPostJump} # replace return address
+jmp {function} # jump to our function
+add esp, 8 # our function returns here due to changed return address
+ret 8
+
+

Technical Limitations

+
+

Wrapper generation does not have understanding of any specific ABI, and as such cannot always be 100% correct in edge cases.

+
+

Wrappers Don't understand ABI Specific Rules

+
+

Some ABIs have unconventional rules for handling edge cases.

+
+

For example, consider the following rule used by the RISC-V ABI.

+
+

When primitive arguments twice the size of a pointer-word are passed on the stack, they are +naturally aligned. When they are passed in the integer registers, they reside in an aligned even-odd +register pair, with the even register holding the least-significant bits. In RV32, for example, the +function void foo(int, long long) is passed its first argument in a0 and its second in a2 and +a3. Nothing is passed in a1.

+
+

The wrappers cannot know or understand the intricate rules such as this that are imposed by an ABI.

+

Allocating Mixed Size Registers is Tricky.

+

Optimized code does not suffer from this bug.

+

The Problem

+

Consider a function which spills a float register xmm0, and an nint (native size integer).
+A Push is basically a sequence of sub and then mov.

+

So (pretend ASM below is valid)

+
push xmm0
+push rax
+
+

Would become

+
sub rsp, 16
+mov [rsp], xmm0
+sub rsp, 8
+mov [rsp], rax
+
+

This is invalid, because the contents of rax will now replace half of the xmm0 register on the stack.
+How ABIs and compilers deal with this isn't always well standardised; some only consider lower bits volatile, +(Microsoft x64) while others don't preserve the bigger registers at all (SystemV x64).

+

Our strategy will be to try rearrange the stack operations to avoid this problem, starting by pushing +smaller registers first, and then larger registers, effectively creating:

+
sub rsp, 8
+mov [rsp], rax
+sub rsp, 16
+mov [rsp], xmm0
+
+

When using Optimized Code

+

Currently with optimizations enabled, this code compiles as:

+
sub rsp, 24
+mov [rsp], xmm0
+mov [rsp + 16], rax
+
+

Which is valid.

+

Wrappers (Currently) Don't understand how to split larger registers.

+

Some calling conventions, have rules where larger values (e.g. 128-bit values on x64) are split into +2 registers.

+

The wrapper generator cannot generate code for these functions currently.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dev/platform/overview/index.html b/dev/platform/overview/index.html new file mode 100644 index 0000000..446726b --- /dev/null +++ b/dev/platform/overview/index.html @@ -0,0 +1,1115 @@ + + + + + + + + + + + + + + + + + + + + + + + + Platform Support - Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Platform Overview

+
+

This page provides a list of platform specific functionality required for supporting Reloaded.Hooks-rs.

+
+
    +
  • Required means library must have this to function.
  • +
  • Recommended means library may not work on some edge cases.
  • +
  • Optional means library can function without it.
  • +
+
+

To add support for new platforms, supply the necessary function pointers in platform_functions.rs.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureWindowsLinuxmacOS
Permission Change
W^X Disable/RestoreN/AN/A [1]⚠️ [2]
Targeted Memory Allocation
+

[1] May be present depending on kernel configuration. Have not done adequate research.
+[2] Needed for Apple Silicon only.

+

How to Implement Support

+
+

Once you're done, submit a PR to add support for your platform.

+
+

Platform Functions

+
+

The library provides a platform_functions.rs file which contains all the platform specific functions.

+
+

Implement the functions in this file for your platform. Generally you'll only need unprotect_memory, +though on some platforms, you may need to implement disable_write_xor_execute and restore_write_xor_execute +as well, depending on the platform's security policy.

+ +
+

For optimal performance, you should add support for your platform to reloaded-memory-buffers.

+
+

It's recommended to use reloaded-hooks-rs alongside reloaded-memory-buffers. The concept of the buffers +library is to perform allocations as close to original code as possible, allowing for more efficient code.

+

This requires walking memory pages. If your OS does not have a way to do this, you can in the meantime use +the built-in DefaultBufferFactory. On some platforms you'll also need to adjust DefaultBufferFactory::create_page_as_rx, +if your platform does not allow RWX allocations.

+
+

For DefaultBufferFactory, you might need to replace mmap_rs in get_any_buffer to use your platform specific page allocation function.

+
+

Testing Your Implementation

+

Platform specific functionality is not unit tested as it relies on OS/system state. Instead, integration +tests are used to test the functionality.

+

Find the tests for a given hook type (recommend: assembly_hook tests) and run them on your platform.

+

If you can't run tests on your platform, copy them to one of your programs manually.

+

(Required) Permission Change

+
+

Many platforms have per-page access permissions; which may prevent certain regions of memory from being modified.

+
+

Notably for the use cases of this library, the .text section is usually non-writeable, which +prevents hooking app functions out of the box.

+

To work around this, the library will call the unprotect function in platform_functions.rs before +making code changes in memory. It will then (for performance reasons) leave the memory unprotected +for the lifetime of the process (assuming it remains unprotected).

+

For the common operating systems; the protect/unprotect functions map to the following API calls:

+
    +
  • Windows: VirtualProtect
  • +
  • Linux & macOS: mprotect
  • +
+

(Required) W^X Disable/Restore

+
+

Only required on Apple, opt in on Linux/Windows but haven't used in a game software in the wild.

+
+
+

Info

+

Some platforms enforce a security protection called 'Write XOR Execute'; where a memory page may only be marked as writeable +OR executable at any moment in time.

+
+ +

To work around this, the library will call the disable_write_xor_execute function in platform_functions.rs +ahead of every function call. It will then call restore_write_xor_execute after.

+ +
+

Info

+

The process of code relocation might require that new location of the code +is within a certain region of the old code, usually 128MiB, 2GiB or 4GiB (depending on platform).

+
+

In this case, you must walk over the memory pages of a process; and find a suitable place to allocate 😉

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..89536d0 --- /dev/null +++ b/index.html @@ -0,0 +1,1154 @@ + + + + + + + + + + + + + + + + + + + + + + Reloaded Hooks NexGen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + + +

Home

+ +
+

Reloaded Hooks NextGen

+ +

+ Cross Platform, Cross Architecture re-implementation of Reloaded.Hooks.
+ 🦀 Now in Crab 🦀 +
+ +

About

+

reloaded-hooks-rs is an enhanced port of the original Reloaded.Hooks (<= 4.3.0) to Rust.

+

This library is written as no_std. Currently support for Windows, Linux and macOS is provided +out of the box. That said, a lot of functionality is platform & architecture agnostic, hopefully making +porting easier.

+

Platform Support

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Platformx86x86_64
Windows✔️✔️
Linux✔️✔️
macOSN/A *✔️
+
    +
  • Apple dropped support for x86 platforms entirely; you can't run x86 code at all.
  • +
+

The reloaded-hooks-rs code is not hardwired to any platform. For other platforms you can fill the +[pending] struct and provide appropriate function pointers; which would possibly make the library work +even in bare metal or embedded environments.

+

Architecture Support

+
+

Lists the currently available library features for different architectures.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Featurex86 & x64ARM64
Basic Function Hooking
Code Relocation✅*
Hook Stacking
Calling Convention Wrapper Generation
Optimal Wrapper Generation
Length Disassembler
+

Bootstrapping a new architecture is not a difficult job!!
+Please see Architecture Support Overview for porting guidance.

+
    +
  • x86 should work in all cases, but x64 isn't tested against all 5000+ instructions.
  • +
+

Feature Support

+
    +
  • Supports common OSes/platforms.
  • +
  • Easy to integrate to new operating systems.
  • +
  • Calling Convention Translation (e.g. __stdcall -> __fastcall).
  • +
  • Strong interoperability. (incl. Hook stacking)
  • +
  • Parameter Injection (inject a 'context' parameter to your hooks).
  • +
  • Branch rewriting.
  • +
  • Mid function x86/x64 hooks (Cheat Engine style).
  • +
  • Optimal code generation.
      +
    • For Relocated Code and Wrappers.
    • +
    • Improved over the common hooking libraries (Minhook, Detours), especially for ARM64.
    • +
    • (The author of this library is an anal optimization freak.)
    • +
    +
  • +
+

Limitations

+

No IP Relocation

+
+

IP relocation is a thread safety technique employed by some libraries whereby all process' threads are stopped and any threads that are executing the prolog of the function that is being detoured at the same time have their instruction pointer overwritten to the hook.

+
+

This can only be done on some OSes that expose the relevant APIs.
+For the project author's use case, this is not needed, however the project would happily accept a +PR for this functionality.

+

In practice this is very, very rarely a problem.

+

Caller Saved Registers Always saved in Entirety

+
+

This applies to calling convention wrappers generated by the library.

+
+
+

I've never seen this requirement in the wild, ever; usually for functions with this many parameters, they use standard ABI, but it's technically possible.

+
+

When generating wrappers between different calling conventions; the library preserves entire registers, +you can't for example specify 'please only preserve the upper 32-bits of register '. As is, currently +only the whole register can be preserved.

+

Technical Questions

+

If you have questions/bug reports/etc. feel free to Open an Issue.

+

Happy Documenting ❤️

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..d23acab --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Reloaded Hooks NextGen Cross Platform, Cross Architecture re-implementation of Reloaded.Hooks. \ud83e\udd80 Now in Crab \ud83e\udd80"},{"location":"#about","title":"About","text":"

reloaded-hooks-rs is an enhanced port of the original Reloaded.Hooks (<= 4.3.0) to Rust.

This library is written as no_std. Currently support for Windows, Linux and macOS is provided out of the box. That said, a lot of functionality is platform & architecture agnostic, hopefully making porting easier.

"},{"location":"#platform-support","title":"Platform Support","text":"Platform x86 x86_64 Windows \u2714\ufe0f \u2714\ufe0f Linux \u2714\ufe0f \u2714\ufe0f macOS N/A * \u2714\ufe0f
  • Apple dropped support for x86 platforms entirely; you can't run x86 code at all.

The reloaded-hooks-rs code is not hardwired to any platform. For other platforms you can fill the [pending] struct and provide appropriate function pointers; which would possibly make the library work even in bare metal or embedded environments.

"},{"location":"#architecture-support","title":"Architecture Support","text":"

Lists the currently available library features for different architectures.

Feature x86 & x64 ARM64 Basic Function Hooking \u2705 \u2705 Code Relocation \u2705* \u2705 Hook Stacking \u2705 \u2705 Calling Convention Wrapper Generation \u2705 \u2705 Optimal Wrapper Generation \u2705 \u2705 Length Disassembler \u2705 \u2705

Bootstrapping a new architecture is not a difficult job!! Please see Architecture Support Overview for porting guidance.

  • x86 should work in all cases, but x64 isn't tested against all 5000+ instructions.
"},{"location":"#feature-support","title":"Feature Support","text":"
  • Supports common OSes/platforms.
  • Easy to integrate to new operating systems.
  • Calling Convention Translation (e.g. __stdcall -> __fastcall).
  • Strong interoperability. (incl. Hook stacking)
  • Parameter Injection (inject a 'context' parameter to your hooks).
  • Branch rewriting.
  • Mid function x86/x64 hooks (Cheat Engine style).
  • Optimal code generation.
    • For Relocated Code and Wrappers.
    • Improved over the common hooking libraries (Minhook, Detours), especially for ARM64.
    • (The author of this library is an anal optimization freak.)
"},{"location":"#limitations","title":"Limitations","text":""},{"location":"#no-ip-relocation","title":"No IP Relocation","text":"

IP relocation is a thread safety technique employed by some libraries whereby all process' threads are stopped and any threads that are executing the prolog of the function that is being detoured at the same time have their instruction pointer overwritten to the hook.

This can only be done on some OSes that expose the relevant APIs. For the project author's use case, this is not needed, however the project would happily accept a PR for this functionality.

In practice this is very, very rarely a problem.

"},{"location":"#caller-saved-registers-always-saved-in-entirety","title":"Caller Saved Registers Always saved in Entirety","text":"

This applies to calling convention wrappers generated by the library.

I've never seen this requirement in the wild, ever; usually for functions with this many parameters, they use standard ABI, but it's technically possible.

When generating wrappers between different calling conventions; the library preserves entire registers, you can't for example specify 'please only preserve the upper 32-bits of register '. As is, currently only the whole register can be preserved."},{"location":"#technical-questions","title":"Technical Questions","text":"

If you have questions/bug reports/etc. feel free to Open an Issue.

Happy Documenting \u2764\ufe0f

"},{"location":"contributing/","title":"Contribution Guidelines","text":"

The wiki provides details on internals of the library. They may help you when contributing \ud83d\ude09.

First off, thank you for considering contributing to reloaded-hooks.

If your contribution is not straightforward, please first discuss the change you wish to make by creating a new issue before making the change. We might be able to discuss general design, etc. before you embark on a huge endeavour.

"},{"location":"contributing/#reporting-issues","title":"Reporting Issues","text":"

Before reporting an issue on the issue tracker, please check that it has not already been reported by searching for some related keywords.

"},{"location":"contributing/#pull-requests","title":"Pull Requests","text":"

Try to do one pull request per change.

"},{"location":"contributing/#commit-names","title":"Commit Names","text":"

Reloaded repositories auto-generate changelogs based on commit names.

When you make git commits; try to stick to the style of Keep a changelog:

  • Added for new features.
  • Changed for changes in existing functionality.
  • Deprecated for soon-to-be removed features.
  • Removed for now removed features.
  • Fixed for any bug fixes.
  • Security in case of vulnerabilities.
"},{"location":"contributing/#code-style","title":"Code Style","text":"

Please use the standard code style cargo fmt, and run the clippy linter (cargo clippy), fixing warnings before submitting PRs.

If you are using VSCode, this should be automated (on Save) per this repository's settings.

"},{"location":"Reloaded/Readme/","title":"Readme","text":"

Please visit the documentation site for usage instructions & more.

"},{"location":"Reloaded/Pages/","title":"Index","text":"The Reloaded MkDocs Theme A Theme for MkDocs Material. That resembles the look of Reloaded."},{"location":"Reloaded/Pages/#about","title":"About","text":"

This it the NexusMods theme for Material-MkDocs, inspired by the look of Reloaded-II.

The overall wiki theme should look fairly close to the actual launcher appearance.

"},{"location":"Reloaded/Pages/#setup-from-scratch","title":"Setup From Scratch","text":"
  • Add this repository as submodule to docs/Reloaded.
  • Save the following configuration as mkdocs.yml in your repository root.
site_name: Reloaded MkDocs Theme\nsite_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\n\nrepo_name: Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\nrepo_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\n\nextra:\nsocial:\n- icon: fontawesome/brands/github\nlink: https://github.com/Reloaded-Project\n- icon: fontawesome/brands/twitter\nlink: https://twitter.com/thesewer56?lang=en-GB\n\nextra_css:\n- Reloaded/Stylesheets/extra.css\n\nmarkdown_extensions:\n- admonition\n- tables\n- pymdownx.details\n- pymdownx.highlight\n- pymdownx.superfences:\ncustom_fences:\n- name: mermaid\nclass: mermaid\nformat: !!python/name:pymdownx.superfences.fence_code_format\n- pymdownx.tasklist\n- def_list\n- meta\n- md_in_html\n- attr_list\n- footnotes\n- pymdownx.tabbed:\nalternate_style: true\n- pymdownx.emoji:\nemoji_index: !!python/name:materialx.emoji.twemoji\nemoji_generator: !!python/name:materialx.emoji.to_svg\n\ntheme:\nname: material\npalette:\nscheme: reloaded-slate\nfeatures:\n- navigation.instant\n\nplugins:\n- search\n\nnav:\n- Home: index.md\n
  • Add a GitHub Actions workload in .github/workflows/DeployMkDocs.yml.
name: DeployMkDocs\n\n# Controls when the action will run. \non:\n# Triggers the workflow on push on the master branch\npush:\nbranches: [ main ]\n\n# Allows you to run this workflow manually from the Actions tab\nworkflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n# This workflow contains a single job called \"build\"\nbuild:\n# The type of runner that the job will run on\nruns-on: ubuntu-latest\n\n# Steps represent a sequence of tasks that will be executed as part of the job\nsteps:\n\n# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n- name: Checkout Branch\nuses: actions/checkout@v2\nwith:\nsubmodules: recursive\n\n# Deploy MkDocs\n- name: Deploy MkDocs\n# You may pin to the exact commit or the version.\n# uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb\nuses: mhausenblas/mkdocs-deploy-gh-pages@master\nenv:\nGITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\nREQUIREMENTS: ./docs/requirements.txt\n
  • Push to GitHub, this should produce a GitHub Pages site.
  • Go to Settings -> Pages in your repo and select gh-pages branch to enable GitHub pages.

Your page should then be live.

Tip

Refer to Contributing for instructions on how to locally edit and modify the wiki.

Note

For Reloaded3 theme use reloaded3-slate instead of reloaded-slate.

"},{"location":"Reloaded/Pages/#extra","title":"Extra","text":"

Info

Most documentation pages will also include additional plugins; some which are used in the pages here. Here is a sample complete mkdocs.yml you can copy to your project for reference.

"},{"location":"Reloaded/Pages/#technical-questions","title":"Technical Questions","text":"

If you have questions/bug reports/etc. feel free to Open an Issue.

Happy Documenting \u2764\ufe0f

"},{"location":"Reloaded/Pages/contributing/","title":"Contributing to the Wiki: Locally","text":"

Info

This page shows you how to contribute to any documentation page or wiki based on this template.

Note

This theme is forked from my theme for Nexus Docs; and this page is synced with that.

"},{"location":"Reloaded/Pages/contributing/#tutorial","title":"Tutorial","text":"

Note

If you are editing the repository with the theme itself on Windows, it might be a good idea to run git config core.symlinks true first to allow git to create symlinks on clone.

You should learn the basics of git, an easy way is to give GitHub Desktop (Tutorial) a go. It's only 15 minutes \ud83d\ude00.

  1. Create a GitHub account.
  2. Fork this repository:

    This will create a copy of the repository on your own user account, which you will be able to edit.

  3. Clone this repository.

    For example, using GitHub Desktop:

  4. Make changes inside the docs folder.

    Consider using a Markdown Cheat Sheet if you are new to markdown.

    I recommend using a markdown editor such as Typora. Personally I just work from inside Rider.

  5. Commit the changes and push to GitHub.

  6. Open a Pull Request.

    Opening a Pull Request will allow us to review your changes before adding them with the main official page. If everything's good, we'll hit the merge button and add your changes to the official repository.

"},{"location":"Reloaded/Pages/contributing/#website-live-preview","title":"Website Live Preview","text":"

If you are working on the wiki locally, you can generate a live preview the full website. Here's a quick guide of how you could do it from your command prompt (cmd).

  1. Install Python 3

    If you have winget installed, or Windows 11, you can do this from the command prompt.

    Windows (winget)Archlinux
    winget install Python.Python.3\n
    pacman -S python-pip # you should already have Python\n

    Otherwise download Python 3 from the official website or package manager.

  2. Install Material for MkDocs and Plugins (Python package)

    Windows/OSXLinux
    # Restart your command prompt before running this command.\npip install mkdocs-material\npip install mkdocs-redirects\n

    On Linux, there is a chance that python might be a core part of your OS, meaning that you ideally shouldn't touch the system installation.

    Use virtual environments instead.

    python -m venv mkdocs # Create the environment\nsource ~/mkdocs/bin/activate # Enter the environment\n\npip install mkdocs-material\npip install mkdocs-redirects\n

    Make sure you enter the environment before any time you run mkdocs.

  3. Open a command prompt in the folder containing mkdocs.yml. and run the site locally.

    # Move to project folder.\ncd <Replace this with full path to folder containing `mkdocs.yml`>\nmkdocs serve\n

    Copy the address to your web browser and enjoy the live preview; any changes you save will be shown instantly.

"},{"location":"Reloaded/Pages/license/","title":"The Reloaded Project License","text":"

Most components of the Reloaded are governed by the GPLv3 license.

In some, albeit rare scenarios, certain libraries might be licensed under LGPLv3 instead.

This is a FAQ meant to clarify the licensing choice and its implications. Please note, though, that the full license text is the final legal authority.

"},{"location":"Reloaded/Pages/license/#why-was-gpl-v3-chosen","title":"Why was GPL v3 chosen?","text":"

The primary objective is to prevent closed-source, commercial exploitation of the project.

We want to ensure that the project isn't used within a proprietary environment for profit-making purposes such as:

  • Being sold behind a Patreon paywall.
  • Being integrated into a closed-source commercial product for sale.

The Reloaded Project is a labour of love from unpaid hobbyist volunteers.

Exploiting that work for profit feels fundamentally unfair.

While the GPLv3 license doesn't prohibit commercial use outright, it does prevent commercial exploitation by requiring that contributions are given back to the open-source community.

In that fashion, everyone can benefit from the projects under the Reloaded label.

"},{"location":"Reloaded/Pages/license/#can-i-use-reloaded-libraries-commercially","title":"Can I use Reloaded Libraries Commercially?","text":"

You can as long as the resulting produce is also licensed under GPLv3, and thus open source.

"},{"location":"Reloaded/Pages/license/#can-i-use-reloaded-libraries-in-a-closed-source-application","title":"Can I use Reloaded Libraries in a closed-source application?","text":"

The license terms do not permit this.

However, if your software is completely non-commercial, meaning it's neither sold for profit, funded in development, nor hidden behind a paywall (like Patreon), we probably just look the other way.

This often applies to non-professional programmers, learners, or those with no intent to exploit the project. We believe in understanding and leniency for those who might not know better.

GPL v3 exists to protect the project and its contributors. If you're not exploiting the project for commercial gain, you're not hurting us; and we will not enforce the terms of the GPL.

If you are interested in obtaining a commercial license, or want an explicit written exemption, please get in touch with the repository owners.

"},{"location":"Reloaded/Pages/license/#can-i-link-reloaded-libraries-staticallydynamically","title":"Can I link Reloaded Libraries statically/dynamically?","text":"

Yes, as long as you adhere to the GPLv3 license terms, you're permitted to statically link Reloaded Libraries into your project, for instance, through the use of NativeAOT or ILMerge.

"},{"location":"Reloaded/Pages/license/#guidelines-for-non-commercial-use","title":"Guidelines for Non-Commercial Use","text":"

We support and encourage the non-commercial use of Reloaded Libraries. Non-commercial use generally refers to the usage of our libraries for personal projects, educational purposes, academic research, or use by non-profit organizations.

"},{"location":"Reloaded/Pages/license/#personal-projects","title":"Personal Projects","text":"

You're free to use our libraries for projects that you undertake for your own learning, hobby or personal enjoyment. This includes creating mods for your favorite games or building your own applications for personal use.

"},{"location":"Reloaded/Pages/license/#educational-use","title":"Educational Use","text":"

Teachers and students are welcome to use our libraries as a learning resource. You can incorporate them into your teaching materials, student projects, coding bootcamps, workshops, etc.

"},{"location":"Reloaded/Pages/license/#academic-research","title":"Academic Research","text":"

Researchers may use our libraries for academic and scholarly research. We'd appreciate if you cite our work in any publications that result from research involving our libraries.

"},{"location":"Reloaded/Pages/license/#non-profit-organizations","title":"Non-profit Organizations","text":"

If you're part of a registered non-profit organization, you can use our libraries in your projects. However, any derivative work that uses our libraries must also be released under the GPL.

Please remember, if your usage of our libraries evolves from non-commercial to commercial, you must ensure compliance with the terms of the GPL v3 license.

"},{"location":"Reloaded/Pages/license/#attribution-requirements","title":"Attribution Requirements","text":"

As Reloaded Project is a labor of love, done purely out of passion and with an aim to contribute to the broader community, we highly appreciate your support in providing attribution when using our libraries.

While not legally mandatory under GPL v3, it is a simple act that can go a long way in recognizing the efforts of our contributors and fostering an open and collaborative atmosphere.

If you choose to provide attribution (and we hope you do!), here are some guidelines:

  • Acknowledge the Use of Reloaded Libraries: Mention that your project uses or is based on Reloaded libraries. This could be in your project's readme, a credits page on a website, a manual, or within the software itself.

  • Link to the Project: If possible, provide a link back to the Reloaded Project. This allows others to explore and potentially benefit from our work.

Remember, attribution is more than just giving credit,,, it's a way of saying thank you \ud83d\udc49\ud83d\udc48, fostering reciprocal respect, and acknowledging the power of collaborative open-source development.

We appreciate your support and look forward to seeing what amazing projects you create using Reloaded libraries!

"},{"location":"Reloaded/Pages/license/#code-from-mitbsd-licensed-projects","title":"Code from MIT/BSD Licensed Projects","text":"

In some rare instances, code from more permissively licensed projects, such as those under the MIT or BSD licenses, may be referenced, incorporated, or slightly modified within the Reloaded Project.

It's important to us to respect the terms and intentions of these permissive licenses, which often allow their code to be used in a wide variety of contexts, including in GPL-licensed projects like ours.

In these cases, the Reloaded Project is committed to clearly disclosing the usage of such code:

  • Method-Level Disclosure: For individual methods or small code snippets, we use appropriate attribution methods, like programming language attributes. For example, methods borrowed or adapted from MIT-licensed projects might be marked with a [MITLicense] attribute.

  • File-Level Disclosure: For larger amounts of code, such as entire files or modules, we'll include the original license text at the top of the file and clearly indicate which portions of the code originate from a differently-licensed project.

  • Project-Level Disclosure: If an entire library or significant portion of a project under a more permissive license is used, we will include an acknowledgment in a prominent location, such as the readme file or the project's license documentation.

This approach ensures we honor the contributions of the open source community at large, respect the original licenses, and maintain transparency with our users about where code originates from.

Any files/methods or snippets marked with those attributes may be consumed using their original license terms.

i.e. If a method is marked with [MITLicense], you may use it under the terms of the MIT license.

"},{"location":"Reloaded/Pages/license/#contributing-to-the-reloaded-project","title":"Contributing to the Reloaded Project","text":"

We welcome and appreciate contributions to the Reloaded Project! By contributing, you agree to share your changes under the same GPLv3 license, helping to make the project better for everyone.

"},{"location":"Reloaded/Pages/testing-zone/","title":"Testing Zone","text":"

Info

This is a dummy page with various Material MkDocs controls and features scattered throughout for testing.

"},{"location":"Reloaded/Pages/testing-zone/#custom-admonitions","title":"Custom Admonitions","text":"

Reloaded Admonition

An admonition featuring a Reloaded logo. My source is in Stylesheets/extra.css as Custom 'reloaded' admonition.

Heart Admonition

An admonition featuring a heart; because we want to contribute back to the open source community. My source is in Stylesheets/extra.css as Custom 'reloaded heart' admonition.

Nexus Admonition

An admonition featuring a Nexus logo. My source is in Stylesheets/extra.css as Custom 'nexus' admonition.

Heart Admonition

An admonition featuring a heart; because we want to contribute back to the open source community. My source is in Stylesheets/extra.css as Custom 'nexus heart' admonition.

"},{"location":"Reloaded/Pages/testing-zone/#mermaid-diagram","title":"Mermaid Diagram","text":"

Flowchart (Source: Nexus Archive Library):

flowchart TD\n    subgraph Block 2\n        BigFile1.bin\n    end\n\n    subgraph Block 1\n        BigFile0.bin\n    end\n\n    subgraph Block 0\n        ModConfig.json -.-> Updates.json \n        Updates.json -.-> more[\"... more .json files\"]        \n    end

Sequence Diagram (Source: Reloaded3 Specification):

sequenceDiagram\n\n    % Define Items\n    participant Mod Loader\n    participant Virtual FileSystem (VFS)\n    participant CRI CPK Archive Support\n    participant Persona 5 Royal Support\n    participant Joker Costume\n\n    % Define Actions\n    Mod Loader->>Persona 5 Royal Support: Load Mod\n    Persona 5 Royal Support->>Mod Loader: Request CRI CPK Archive Support API\n    Mod Loader->>Persona 5 Royal Support: Receive CRI CPK Archive Support Instance\n\n    Mod Loader->>Joker Costume: Load Mod\n    Mod Loader-->Persona 5 Royal Support: Notification: 'Loaded Joker Costume'\n    Persona 5 Royal Support->>CRI CPK Archive Support: Add Files from 'Joker Costume' to CPK Archive (via API)

State Diagram (Source: Mermaid Docs):

stateDiagram-v2\n    [*] --> Still\n    Still --> [*]\n\n    Still --> Moving\n    Moving --> Still\n    Moving --> Crash\n    Crash --> [*]

Class Diagram (Arbitrary)

classDiagram\n    class Animal\n    `NexusMobile\u2122` <|-- Car

Note

At time of writing, version of Mermaid is a bit outdated here; and other diagrams might not render correctly (even on unmodified theme); thus certain diagrams have been omitted from here.

"},{"location":"Reloaded/Pages/testing-zone/#code-block","title":"Code Block","text":"

Snippet from C# version of Sewer's Virtual FileSystem (VFS):

/// <summary>\n/// Tries to get files for a specific folder, assuming the input path is already in upper case.\n/// </summary>\n/// <param name=\"folderPath\">The folder to find. Already lowercase.</param>\n/// <param name=\"value\">The returned folder instance.</param>\n/// <returns>True if found, else false.</returns>\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\npublic bool TryGetFolderUpper(ReadOnlySpan<char> folderPath, out SpanOfCharDict<TTarget> value)\n{\n// Must be O(1)\nvalue = default!;        // Compare equality.\n// Note to devs: Do not invert branches, we optimise for hot paths here.\nif (folderPath.StartsWith(Prefix))\n{\n// Check for subfolder in branchless way.\n// In CLR, bool is length 1, so conversion to byte should be safe.\n// Even suppose it is not; as long as code is little endian; truncating int/4 bytes to byte still results \n// in correct answer.\nvar hasSubfolder = Prefix.Length != folderPath.Length;\nvar hasSubfolderByte = Unsafe.As<bool, byte>(ref hasSubfolder);\nvar nextFolder = folderPath.SliceFast(Prefix.Length + hasSubfolderByte);\n\nreturn SubfolderToFiles.TryGetValue(nextFolder, out value!);\n}\n\nreturn false;\n}\n

Something more number heavy, Fast Inverse Square Root from Quake III Arena (unmodified).

float Q_rsqrt( float number )\n{\nlong i;\nfloat x2, y;\nconst float threehalfs = 1.5F;\n\nx2 = number * 0.5F;\ny  = number;\ni  = * ( long * ) &y;                       // evil floating point bit level hacking\ni  = 0x5f3759df - ( i >> 1 );               // what the fuck? \ny  = * ( float * ) &i;\ny  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration\n//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed\n\nreturn y;\n}\n

"},{"location":"Reloaded/Pages/testing-zone/#default-admonitions","title":"Default Admonitions","text":"

Note

Test

Abstract

Test

Info

Test

Tip

Test

Success

Test

Question

Test

Warning

Test

Failure

Test

Danger

Test

Bug

Test

Example

Test

Quote

Test

"},{"location":"Reloaded/Pages/testing-zone/#tables","title":"Tables","text":"Method Description GET Fetch resource PUT Update resource DELETE Delete resource"},{"location":"Reloaded/docs/Pages/","title":"Index","text":"The Reloaded MkDocs Theme A Theme for MkDocs Material. That resembles the look of Reloaded."},{"location":"Reloaded/docs/Pages/#about","title":"About","text":"

This it the NexusMods theme for Material-MkDocs, inspired by the look of Reloaded-II.

The overall wiki theme should look fairly close to the actual launcher appearance.

"},{"location":"Reloaded/docs/Pages/#setup-from-scratch","title":"Setup From Scratch","text":"
  • Add this repository as submodule to docs/Reloaded.
  • Save the following configuration as mkdocs.yml in your repository root.
site_name: Reloaded MkDocs Theme\nsite_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\n\nrepo_name: Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\nrepo_url: https://github.com/Reloaded-Project/Reloaded.MkDocsMaterial.Themes.R2\n\nextra:\nsocial:\n- icon: fontawesome/brands/github\nlink: https://github.com/Reloaded-Project\n- icon: fontawesome/brands/twitter\nlink: https://twitter.com/thesewer56?lang=en-GB\n\nextra_css:\n- Reloaded/Stylesheets/extra.css\n\nmarkdown_extensions:\n- admonition\n- tables\n- pymdownx.details\n- pymdownx.highlight\n- pymdownx.superfences:\ncustom_fences:\n- name: mermaid\nclass: mermaid\nformat: !!python/name:pymdownx.superfences.fence_code_format\n- pymdownx.tasklist\n- def_list\n- meta\n- md_in_html\n- attr_list\n- footnotes\n- pymdownx.tabbed:\nalternate_style: true\n- pymdownx.emoji:\nemoji_index: !!python/name:materialx.emoji.twemoji\nemoji_generator: !!python/name:materialx.emoji.to_svg\n\ntheme:\nname: material\npalette:\nscheme: reloaded-slate\nfeatures:\n- navigation.instant\n\nplugins:\n- search\n\nnav:\n- Home: index.md\n
  • Add a GitHub Actions workload in .github/workflows/DeployMkDocs.yml.
name: DeployMkDocs\n\n# Controls when the action will run. \non:\n# Triggers the workflow on push on the master branch\npush:\nbranches: [ main ]\n\n# Allows you to run this workflow manually from the Actions tab\nworkflow_dispatch:\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n# This workflow contains a single job called \"build\"\nbuild:\n# The type of runner that the job will run on\nruns-on: ubuntu-latest\n\n# Steps represent a sequence of tasks that will be executed as part of the job\nsteps:\n\n# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n- name: Checkout Branch\nuses: actions/checkout@v2\nwith:\nsubmodules: recursive\n\n# Deploy MkDocs\n- name: Deploy MkDocs\n# You may pin to the exact commit or the version.\n# uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb\nuses: mhausenblas/mkdocs-deploy-gh-pages@master\nenv:\nGITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\nREQUIREMENTS: ./docs/requirements.txt\n
  • Push to GitHub, this should produce a GitHub Pages site.
  • Go to Settings -> Pages in your repo and select gh-pages branch to enable GitHub pages.

Your page should then be live.

Tip

Refer to Contributing for instructions on how to locally edit and modify the wiki.

Note

For Reloaded3 theme use reloaded3-slate instead of reloaded-slate.

"},{"location":"Reloaded/docs/Pages/#extra","title":"Extra","text":"

Info

Most documentation pages will also include additional plugins; some which are used in the pages here. Here is a sample complete mkdocs.yml you can copy to your project for reference.

"},{"location":"Reloaded/docs/Pages/#technical-questions","title":"Technical Questions","text":"

If you have questions/bug reports/etc. feel free to Open an Issue.

Happy Documenting \u2764\ufe0f

"},{"location":"Reloaded/docs/Pages/contributing/","title":"Contributing to the Wiki: Locally","text":"

Info

This page shows you how to contribute to any documentation page or wiki based on this template.

Note

This theme is forked from my theme for Nexus Docs; and this page is synced with that.

"},{"location":"Reloaded/docs/Pages/contributing/#tutorial","title":"Tutorial","text":"

Note

If you are editing the repository with the theme itself on Windows, it might be a good idea to run git config core.symlinks true first to allow git to create symlinks on clone.

You should learn the basics of git, an easy way is to give GitHub Desktop (Tutorial) a go. It's only 15 minutes \ud83d\ude00.

  1. Create a GitHub account.
  2. Fork this repository:

    This will create a copy of the repository on your own user account, which you will be able to edit.

  3. Clone this repository.

    For example, using GitHub Desktop:

  4. Make changes inside the docs folder.

    Consider using a Markdown Cheat Sheet if you are new to markdown.

    I recommend using a markdown editor such as Typora. Personally I just work from inside Rider.

  5. Commit the changes and push to GitHub.

  6. Open a Pull Request.

    Opening a Pull Request will allow us to review your changes before adding them with the main official page. If everything's good, we'll hit the merge button and add your changes to the official repository.

"},{"location":"Reloaded/docs/Pages/contributing/#website-live-preview","title":"Website Live Preview","text":"

If you are working on the wiki locally, you can generate a live preview the full website. Here's a quick guide of how you could do it from your command prompt (cmd).

  1. Install Python 3

    If you have winget installed, or Windows 11, you can do this from the command prompt.

    Windows (winget)Archlinux
    winget install Python.Python.3\n
    pacman -S python-pip # you should already have Python\n

    Otherwise download Python 3 from the official website or package manager.

  2. Install Material for MkDocs and Plugins (Python package)

    Windows/OSXLinux
    # Restart your command prompt before running this command.\npip install mkdocs-material\npip install mkdocs-redirects\n

    On Linux, there is a chance that python might be a core part of your OS, meaning that you ideally shouldn't touch the system installation.

    Use virtual environments instead.

    python -m venv mkdocs # Create the environment\nsource ~/mkdocs/bin/activate # Enter the environment\n\npip install mkdocs-material\npip install mkdocs-redirects\n

    Make sure you enter the environment before any time you run mkdocs.

  3. Open a command prompt in the folder containing mkdocs.yml. and run the site locally.

    # Move to project folder.\ncd <Replace this with full path to folder containing `mkdocs.yml`>\nmkdocs serve\n

    Copy the address to your web browser and enjoy the live preview; any changes you save will be shown instantly.

"},{"location":"Reloaded/docs/Pages/license/","title":"The Reloaded Project License","text":"

Most components of the Reloaded are governed by the GPLv3 license.

In some, albeit rare scenarios, certain libraries might be licensed under LGPLv3 instead.

This is a FAQ meant to clarify the licensing choice and its implications. Please note, though, that the full license text is the final legal authority.

"},{"location":"Reloaded/docs/Pages/license/#why-was-gpl-v3-chosen","title":"Why was GPL v3 chosen?","text":"

The primary objective is to prevent closed-source, commercial exploitation of the project.

We want to ensure that the project isn't used within a proprietary environment for profit-making purposes such as:

  • Being sold behind a Patreon paywall.
  • Being integrated into a closed-source commercial product for sale.

The Reloaded Project is a labour of love from unpaid hobbyist volunteers.

Exploiting that work for profit feels fundamentally unfair.

While the GPLv3 license doesn't prohibit commercial use outright, it does prevent commercial exploitation by requiring that contributions are given back to the open-source community.

In that fashion, everyone can benefit from the projects under the Reloaded label.

"},{"location":"Reloaded/docs/Pages/license/#can-i-use-reloaded-libraries-commercially","title":"Can I use Reloaded Libraries Commercially?","text":"

You can as long as the resulting produce is also licensed under GPLv3, and thus open source.

"},{"location":"Reloaded/docs/Pages/license/#can-i-use-reloaded-libraries-in-a-closed-source-application","title":"Can I use Reloaded Libraries in a closed-source application?","text":"

The license terms do not permit this.

However, if your software is completely non-commercial, meaning it's neither sold for profit, funded in development, nor hidden behind a paywall (like Patreon), we probably just look the other way.

This often applies to non-professional programmers, learners, or those with no intent to exploit the project. We believe in understanding and leniency for those who might not know better.

GPL v3 exists to protect the project and its contributors. If you're not exploiting the project for commercial gain, you're not hurting us; and we will not enforce the terms of the GPL.

If you are interested in obtaining a commercial license, or want an explicit written exemption, please get in touch with the repository owners.

"},{"location":"Reloaded/docs/Pages/license/#can-i-link-reloaded-libraries-staticallydynamically","title":"Can I link Reloaded Libraries statically/dynamically?","text":"

Yes, as long as you adhere to the GPLv3 license terms, you're permitted to statically link Reloaded Libraries into your project, for instance, through the use of NativeAOT or ILMerge.

"},{"location":"Reloaded/docs/Pages/license/#guidelines-for-non-commercial-use","title":"Guidelines for Non-Commercial Use","text":"

We support and encourage the non-commercial use of Reloaded Libraries. Non-commercial use generally refers to the usage of our libraries for personal projects, educational purposes, academic research, or use by non-profit organizations.

"},{"location":"Reloaded/docs/Pages/license/#personal-projects","title":"Personal Projects","text":"

You're free to use our libraries for projects that you undertake for your own learning, hobby or personal enjoyment. This includes creating mods for your favorite games or building your own applications for personal use.

"},{"location":"Reloaded/docs/Pages/license/#educational-use","title":"Educational Use","text":"

Teachers and students are welcome to use our libraries as a learning resource. You can incorporate them into your teaching materials, student projects, coding bootcamps, workshops, etc.

"},{"location":"Reloaded/docs/Pages/license/#academic-research","title":"Academic Research","text":"

Researchers may use our libraries for academic and scholarly research. We'd appreciate if you cite our work in any publications that result from research involving our libraries.

"},{"location":"Reloaded/docs/Pages/license/#non-profit-organizations","title":"Non-profit Organizations","text":"

If you're part of a registered non-profit organization, you can use our libraries in your projects. However, any derivative work that uses our libraries must also be released under the GPL.

Please remember, if your usage of our libraries evolves from non-commercial to commercial, you must ensure compliance with the terms of the GPL v3 license.

"},{"location":"Reloaded/docs/Pages/license/#attribution-requirements","title":"Attribution Requirements","text":"

As Reloaded Project is a labor of love, done purely out of passion and with an aim to contribute to the broader community, we highly appreciate your support in providing attribution when using our libraries.

While not legally mandatory under GPL v3, it is a simple act that can go a long way in recognizing the efforts of our contributors and fostering an open and collaborative atmosphere.

If you choose to provide attribution (and we hope you do!), here are some guidelines:

  • Acknowledge the Use of Reloaded Libraries: Mention that your project uses or is based on Reloaded libraries. This could be in your project's readme, a credits page on a website, a manual, or within the software itself.

  • Link to the Project: If possible, provide a link back to the Reloaded Project. This allows others to explore and potentially benefit from our work.

Remember, attribution is more than just giving credit,,, it's a way of saying thank you \ud83d\udc49\ud83d\udc48, fostering reciprocal respect, and acknowledging the power of collaborative open-source development.

We appreciate your support and look forward to seeing what amazing projects you create using Reloaded libraries!

"},{"location":"Reloaded/docs/Pages/license/#code-from-mitbsd-licensed-projects","title":"Code from MIT/BSD Licensed Projects","text":"

In some rare instances, code from more permissively licensed projects, such as those under the MIT or BSD licenses, may be referenced, incorporated, or slightly modified within the Reloaded Project.

It's important to us to respect the terms and intentions of these permissive licenses, which often allow their code to be used in a wide variety of contexts, including in GPL-licensed projects like ours.

In these cases, the Reloaded Project is committed to clearly disclosing the usage of such code:

  • Method-Level Disclosure: For individual methods or small code snippets, we use appropriate attribution methods, like programming language attributes. For example, methods borrowed or adapted from MIT-licensed projects might be marked with a [MITLicense] attribute.

  • File-Level Disclosure: For larger amounts of code, such as entire files or modules, we'll include the original license text at the top of the file and clearly indicate which portions of the code originate from a differently-licensed project.

  • Project-Level Disclosure: If an entire library or significant portion of a project under a more permissive license is used, we will include an acknowledgment in a prominent location, such as the readme file or the project's license documentation.

This approach ensures we honor the contributions of the open source community at large, respect the original licenses, and maintain transparency with our users about where code originates from.

Any files/methods or snippets marked with those attributes may be consumed using their original license terms.

i.e. If a method is marked with [MITLicense], you may use it under the terms of the MIT license.

"},{"location":"Reloaded/docs/Pages/license/#contributing-to-the-reloaded-project","title":"Contributing to the Reloaded Project","text":"

We welcome and appreciate contributions to the Reloaded Project! By contributing, you agree to share your changes under the same GPLv3 license, helping to make the project better for everyone.

"},{"location":"Reloaded/docs/Pages/testing-zone/","title":"Testing Zone","text":"

Info

This is a dummy page with various Material MkDocs controls and features scattered throughout for testing.

"},{"location":"Reloaded/docs/Pages/testing-zone/#custom-admonitions","title":"Custom Admonitions","text":"

Reloaded Admonition

An admonition featuring a Reloaded logo. My source is in Stylesheets/extra.css as Custom 'reloaded' admonition.

Heart Admonition

An admonition featuring a heart; because we want to contribute back to the open source community. My source is in Stylesheets/extra.css as Custom 'reloaded heart' admonition.

Nexus Admonition

An admonition featuring a Nexus logo. My source is in Stylesheets/extra.css as Custom 'nexus' admonition.

Heart Admonition

An admonition featuring a heart; because we want to contribute back to the open source community. My source is in Stylesheets/extra.css as Custom 'nexus heart' admonition.

"},{"location":"Reloaded/docs/Pages/testing-zone/#mermaid-diagram","title":"Mermaid Diagram","text":"

Flowchart (Source: Nexus Archive Library):

flowchart TD\n    subgraph Block 2\n        BigFile1.bin\n    end\n\n    subgraph Block 1\n        BigFile0.bin\n    end\n\n    subgraph Block 0\n        ModConfig.json -.-> Updates.json \n        Updates.json -.-> more[\"... more .json files\"]        \n    end

Sequence Diagram (Source: Reloaded3 Specification):

sequenceDiagram\n\n    % Define Items\n    participant Mod Loader\n    participant Virtual FileSystem (VFS)\n    participant CRI CPK Archive Support\n    participant Persona 5 Royal Support\n    participant Joker Costume\n\n    % Define Actions\n    Mod Loader->>Persona 5 Royal Support: Load Mod\n    Persona 5 Royal Support->>Mod Loader: Request CRI CPK Archive Support API\n    Mod Loader->>Persona 5 Royal Support: Receive CRI CPK Archive Support Instance\n\n    Mod Loader->>Joker Costume: Load Mod\n    Mod Loader-->Persona 5 Royal Support: Notification: 'Loaded Joker Costume'\n    Persona 5 Royal Support->>CRI CPK Archive Support: Add Files from 'Joker Costume' to CPK Archive (via API)

State Diagram (Source: Mermaid Docs):

stateDiagram-v2\n    [*] --> Still\n    Still --> [*]\n\n    Still --> Moving\n    Moving --> Still\n    Moving --> Crash\n    Crash --> [*]

Class Diagram (Arbitrary)

classDiagram\n    class Animal\n    `NexusMobile\u2122` <|-- Car

Note

At time of writing, version of Mermaid is a bit outdated here; and other diagrams might not render correctly (even on unmodified theme); thus certain diagrams have been omitted from here.

"},{"location":"Reloaded/docs/Pages/testing-zone/#code-block","title":"Code Block","text":"

Snippet from C# version of Sewer's Virtual FileSystem (VFS):

/// <summary>\n/// Tries to get files for a specific folder, assuming the input path is already in upper case.\n/// </summary>\n/// <param name=\"folderPath\">The folder to find. Already lowercase.</param>\n/// <param name=\"value\">The returned folder instance.</param>\n/// <returns>True if found, else false.</returns>\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\npublic bool TryGetFolderUpper(ReadOnlySpan<char> folderPath, out SpanOfCharDict<TTarget> value)\n{\n// Must be O(1)\nvalue = default!;        // Compare equality.\n// Note to devs: Do not invert branches, we optimise for hot paths here.\nif (folderPath.StartsWith(Prefix))\n{\n// Check for subfolder in branchless way.\n// In CLR, bool is length 1, so conversion to byte should be safe.\n// Even suppose it is not; as long as code is little endian; truncating int/4 bytes to byte still results \n// in correct answer.\nvar hasSubfolder = Prefix.Length != folderPath.Length;\nvar hasSubfolderByte = Unsafe.As<bool, byte>(ref hasSubfolder);\nvar nextFolder = folderPath.SliceFast(Prefix.Length + hasSubfolderByte);\n\nreturn SubfolderToFiles.TryGetValue(nextFolder, out value!);\n}\n\nreturn false;\n}\n

Something more number heavy, Fast Inverse Square Root from Quake III Arena (unmodified).

float Q_rsqrt( float number )\n{\nlong i;\nfloat x2, y;\nconst float threehalfs = 1.5F;\n\nx2 = number * 0.5F;\ny  = number;\ni  = * ( long * ) &y;                       // evil floating point bit level hacking\ni  = 0x5f3759df - ( i >> 1 );               // what the fuck? \ny  = * ( float * ) &i;\ny  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration\n//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed\n\nreturn y;\n}\n

"},{"location":"Reloaded/docs/Pages/testing-zone/#default-admonitions","title":"Default Admonitions","text":"

Note

Test

Abstract

Test

Info

Test

Tip

Test

Success

Test

Question

Test

Warning

Test

Failure

Test

Danger

Test

Bug

Test

Example

Test

Quote

Test

"},{"location":"Reloaded/docs/Pages/testing-zone/#tables","title":"Tables","text":"Method Description GET Fetch resource PUT Update resource DELETE Delete resource"},{"location":"dev/arch/operations-impl/","title":"Operations","text":"

This page tells you which Operations are currently implemented for each architecture.

  • \u274c Means it is not implemented.
  • \u2705 Means it is implemented.
  • \u2753 Means 'not applicable'.
"},{"location":"dev/arch/operations-impl/#needed-for-basic-hooking-support","title":"Needed for Basic Hooking Support","text":""},{"location":"dev/arch/operations-impl/#jumprelative","title":"JumpRelative","text":"Architecture Supported Notes x64 \u2705 +-2GiB x86 \u2705 +-2GiB ARM64 (+- 128MiB) \u2705 +-128MiB ARM64 (+- 4GiB) \u2705 Uses 3 instructions. Used if within range."},{"location":"dev/arch/operations-impl/#jumpabsolute","title":"JumpAbsolute","text":"Architecture Supported Notes x64 \u2705 Uses scratch register for efficiency. x86 \u2705 Uses scratch register for efficiency. ARM64 \u2705 Uses scratch register (required)"},{"location":"dev/arch/operations-impl/#jumpabsoluteindirect","title":"JumpAbsoluteIndirect","text":"Architecture Supported Notes x86 \u2705 x86 \u2705 ARM64 \u274c Variant 0. ARM64 \u2705 Variant 1. Replaced with JumpAbsolute, for perf reasons."},{"location":"dev/arch/operations-impl/#needed-for-wrapper-generation","title":"Needed for Wrapper Generation","text":""},{"location":"dev/arch/operations-impl/#mov","title":"Mov","text":"Architecture Register to Register Vector to Vector x64 \u2705 \u2705 x86 \u2705 \u2705 ARM64 \u2705 \u2705"},{"location":"dev/arch/operations-impl/#movfromstack","title":"MovFromStack","text":"Architecture to Register to Vector x64 \u2705 \u2705 x86 \u2705 \u2705 ARM64 \u2705 \u2705"},{"location":"dev/arch/operations-impl/#movtostack","title":"MovToStack","text":"Architecture to Register to Vector x64 \u2705 \u2705 x86 \u2705 \u2705 ARM64* \u274c \u274c

This is not needed for optimal code generation on ARM64, thus was not implemented.

"},{"location":"dev/arch/operations-impl/#push","title":"Push","text":"Architecture Register Vector x64 \u2705 \u2705 x86 \u2705 \u2705 ARM64 \u2705 \u2705"},{"location":"dev/arch/operations-impl/#pushstack","title":"PushStack","text":"Architecture Supported Notes x64 \u2705 x86 \u2705 ARM64 \u2705 Will use vector registers when available."},{"location":"dev/arch/operations-impl/#pushconstant","title":"PushConstant","text":"Architecture Supported Notes x64 \u2705 x86 \u2705 ARM64 \u2705 2-5 instructions, depending on constant length."},{"location":"dev/arch/operations-impl/#stackalloc","title":"StackAlloc","text":"Architecture Supported x64 \u2705 x86 \u2705 ARM64 \u2705"},{"location":"dev/arch/operations-impl/#pop","title":"Pop","text":"Architecture to Register to Vector Notes x64 \u2705 \u2705 x86 \u2705 \u2705 ARM64 \u2705 \u2705"},{"location":"dev/arch/operations-impl/#xchg","title":"XChg","text":"Architecture Registers Vectors Notes x64 \u2705 \u2705 * *Requires scratch register x86 \u2705 \u2705 * *Requires scratch register ARM64 \u2705 * \u2705 * *Requires scratch register"},{"location":"dev/arch/operations-impl/#callabsolute","title":"CallAbsolute","text":"Architecture Supported Notes x64 (register) \u2705 Uses scratch register for efficiency. x86 (register) \u2705 Uses scratch register for efficiency. ARM64 (register) \u2705 Uses scratch register (required)"},{"location":"dev/arch/operations-impl/#callrelative","title":"CallRelative","text":"Architecture Supported Notes x64 \u2705 +-2GiB x86 \u2705 +-2GiB ARM64 \u2705 +-128MiB"},{"location":"dev/arch/operations-impl/#return","title":"Return","text":"Architecture Supported Notes x64 \u2705 x86 \u2705 ARM64 \u2705 2 instructions if offset > 0."},{"location":"dev/arch/operations-impl/#architecture-specific-operations","title":"Architecture Specific Operations","text":""},{"location":"dev/arch/operations-impl/#calliprelative","title":"CallIpRelative","text":"Architecture Supported Notes x64 \u2705 x86 \u2753 Unsupported. ARM64 (+- 1MiB) \u2705 2 instructions. ARM64 (+- 4GiB) \u2705 3 instructions."},{"location":"dev/arch/operations-impl/#jumpiprelative","title":"JumpIpRelative","text":"Architecture Supported Notes x64 \u2705 x86 \u2753 Unsupported. ARM64 (+- 1MiB) \u2705 2 instructions. ARM64 (+- 4GiB) \u2705 3 instructions."},{"location":"dev/arch/operations-impl/#optimized-pushpop-operations","title":"Optimized Push/Pop Operations","text":""},{"location":"dev/arch/operations-impl/#multipush","title":"MultiPush","text":"Architecture Supported Notes x64* \u2705 x86* \u2705 ARM64 \u2705 Might fall back to single pop/push if mixing register sizes.

* Implemented but not used, due to more efficient code generation alternative.

"},{"location":"dev/arch/operations-impl/#multipop","title":"MultiPop","text":"Architecture Supported Notes x64* \u2705 x86* \u2705 ARM64 \u2705 Might fall back to single pop/push if mixing register sizes.

* Implemented but not used, due to more efficient code generation alternative.

"},{"location":"dev/arch/operations/","title":"Operations","text":"

This page provides a reference for all of the various 'operations' implemented by individual JIT(s).

For more information about each of the operations, see the source code \ud83d\ude09 (enum Operation<T>).

"},{"location":"dev/arch/operations/#needed-for-basic-hooking-support","title":"Needed for Basic Hooking Support","text":""},{"location":"dev/arch/operations/#jumprelative","title":"JumpRelative","text":"

Represents jumping to a relative offset from current instruction pointer.

Rustx64 (+- 2GiB)ARM64 (+- 128MiB)ARM64 (+- 4GiB)x86 (+- 2GiB)
let jump_rel = JumpRelativeOperation {\ntarget_address: 0x200,\n};\n
jmp 0x200 ; Jump to address at current IP + 0x200\n
b 0x200 ; Branch to address at current IP + 0x200\n
adrp x9, #0          ; Load 4K page, relative to PC. (round address down to 4096)\nadd x9, x9, #100     ; Add any missing offset.\nblr x9               ; Branch to location\n
jmp 0x200 ; Jump to address at current IP + 0x200\n
"},{"location":"dev/arch/operations/#jumpabsolute","title":"JumpAbsolute","text":"

Represents jumping to an absolute address stored in a register.

JIT is free to encode this as a relative branch if it's possible.

Rustx64ARM64x86
let jump_abs = JumpAbsoluteOperation {\nscratch_register: rax,\ntarget_address: 0x123456,\n};\n
mov rax, 0x123456 ; Move target address into rax\njmp rax ; Jump to address in rax\n
MOVZ x9, #0x3456        ; Set lower bits.\nMOVK x9, #0x12, LSL #16 ; Move upper bits\nbr x9                   ; Branch to location\n
mov eax, 0x123456 ; Move target address into eax\njmp eax ; Jump to address in eax\n

We prefer this approach to absolute jump because it is faster performance wise.

"},{"location":"dev/arch/operations/#jumpabsoluteindirect","title":"JumpAbsoluteIndirect","text":"

Represents jumping to an absolute address stored in a memory address.

Rustx64 (< 2GiB)x86 (< 2GiB)ARM64 (3 instructions) Variant 0ARM64 (4-6 instructions) Variant 1
let jump_ind = JumpIndirectOperation {\ntarget_address: 0x123456,\n};\n
jmp qword [0x123456] ; Jump to address stored at 0x123456\n
jmp dword [0x123456] ; Jump to address stored at 0x123456\n
; Possible on Multiple of 0x10000 with offset 0-4096\nMOVZ x9, #0x123, LSL #16 ; Store upper 16 bits.\nLDR  x9, [x9, #0x456]    ; Load lower 12 bit offset\nbr x9                    ; Branch to location\n
; On any address up to 4GiB + 4096\nMOVZ x9, #0x3456        ; Set lower bits.\nMOVK x9, #0x12, LSL #16 ; Move upper bits\n; Continue until desired address.\nLDR  x9, [x9, #0x0]     ; Load from address.\nbr x9\n
  • Values in brackets indicate max address usable.

On MacOS, this is not usable, because memory < 2GiB is restricted from access.

"},{"location":"dev/arch/operations/#needed-for-wrapper-generation","title":"Needed for Wrapper Generation","text":"

This includes functionality like 'parameter injection'.

"},{"location":"dev/arch/operations/#mov","title":"Mov","text":"

Represents a move operation between two registers.

Rustx64ARM64x86
let move_op = MovOperation {\nsource: r8,\ntarget: r9,  };\n
mov r9, r8 ; Move r8 into r9\n
mov x9, x8 ; Move x8 into x9\n
mov ebx, eax ; Move eax into ebx\n
"},{"location":"dev/arch/operations/#movfromstack","title":"MovFromStack","text":"

Represents a move operation from the stack into a register.

Rustx64ARM64x86
let move_from_stack = MovFromStackOperation {\nstack_offset: 8,\ntarget: rbx,\n};\n
mov rbx, [rsp + 8] ; Move value at rsp + 8 into rbx\n
ldr x9, [sp, #8] ; Load value at sp + 8 into x9\n
mov ebx, [esp + 8] ; Move value at esp + 8 into ebx\n
"},{"location":"dev/arch/operations/#movtostack","title":"MovToStack","text":"

Represents moving a register value onto the stack at a user specified offset.

Rustx64ARM64x86
let mov_to_stack = MovToStackOperation {\nregister: rbx,\nstack_offset: 16,  };\n
mov [rsp + 16], rbx ; Move rbx onto the stack 16 bytes above rsp \n
str x9, [sp, #16] ; Store x9 onto the stack 16 bytes above sp\n
mov [esp + 16], ebx ; Move ebx onto the stack 16 bytes above esp\n
"},{"location":"dev/arch/operations/#push","title":"Push","text":"

Represents pushing a register onto the stack.

Rustx64ARM64x86
let push = PushOperation {\nregister: r9,\n};\n
push r9 ; Push rbx onto the stack\n
sub sp, sp, #8 ; Decrement stack pointer\nstr x9, [sp] ; Store x9 on the stack\n
push ebx ; Push ebx onto the stack\n
"},{"location":"dev/arch/operations/#pushstack","title":"PushStack","text":"

Represents pushing a value from the stack to the stack.

Rustx64ARM64x86
let push_stack = PushStackOperation {\noffset: 8,\nitem_size: 8,\n};\n
push qword [rsp + 8] ; Push value at rsp + 8 onto the stack\n
ldr x9, [sp, #8] ; Load value at sp + 8 into x9\nsub sp, sp, #8 ; Decrement stack pointer\nstr x9, [sp] ; Push x9 onto the stack\n
push [esp + 8] ; Push value at esp + 8 onto the stack\n
"},{"location":"dev/arch/operations/#pushconstant","title":"PushConstant","text":"

Represents pushing a constant value onto the stack.

Rustx64ARM64x86
let push_const = PushConstantOperation {\nvalue: 10,\n};\n
push 10 ; Push constant value 10 onto stack\n
sub sp, sp, #8 ; Decrement stack pointer\nmov x9, 10 ; Move constant 10 into x9\nstr x9, [sp] ; Store x9 on the stack\n
push 10 ; Push constant value 10 onto stack\n
"},{"location":"dev/arch/operations/#stackalloc","title":"StackAlloc","text":"

Represents adjusting the stack pointer.

Rustx64ARM64x86
let stack_alloc = StackAllocOperation {\noperand: 8,\n};\n
sub rsp, 8 ; Decrement rsp by 8\n
sub sp, sp, #8 ; Decrement sp by 8\n
sub esp, 8 ; Decrement esp by 8\n
"},{"location":"dev/arch/operations/#pop","title":"Pop","text":"

Represents popping a value from the stack into a register.

Rustx64ARM64x86
let pop = PopOperation {\nregister: rbx,\n};\n
pop rbx ; Pop value from stack into rbx\n
ldr x9, [sp] ; Load stack top into x9\nadd sp, sp, #8 ; Increment stack pointer\n
pop ebx ; Pop value from stack into ebx\n
"},{"location":"dev/arch/operations/#xchg","title":"XChg","text":"

Represents exchanging the contents of two registers.

On some architectures (e.g. ARM64) this requires a scratch register.

Rustx64ARM64x86
let xchg = XChgOperation {\nregister1: r9,\nregister2: r8,\nscratch: None,\n};\n
xchg r8, r9 ; Swap r8 and r9\n
// ARM doesn't have xchg instruction\nmov x10, x8 ; Move x8 into x10 (scratch register)\nmov x8, x9 ; Move x9 into x8\nmov x9, x10 ; Move original x8 (in x10) into x9\n
xchg eax, ebx ; Swap eax and ebx\n
"},{"location":"dev/arch/operations/#callabsolute","title":"CallAbsolute","text":"

Represents calling an absolute address stored in a register or memory.

Rustx64ARM64x86
let call_abs = CallAbsoluteOperation {\nscratch_register: r9,\ntarget_address: 0x123456,\n};\n
mov rax, 0x123456 ; Move target address into rax\ncall r9 ; Call address in rax\n
adr x9, target_func ; Load address of target function into x9\nblr x9 ; Branch and link to address in x9\n
mov eax, 0x123456 ; Move target address into eax\ncall eax ; Call address in eax\n
"},{"location":"dev/arch/operations/#callrelative","title":"CallRelative","text":"

Represents calling a relative offset from current instruction pointer.

Rustx64ARM64x86
let call_rel = CallRelativeOperation {\ntarget_address: 0x200,\n};\n
call 0x200 ; Call address at current IP + 0x200\n
bl 0x200 ; Branch with link to address at current IP + 0x200\n
call 0x200 ; Call address at current IP + 0x200\n
"},{"location":"dev/arch/operations/#return","title":"Return","text":"

Represents returning from a function call.

Rustx64ARM64x86
let ret = ReturnOperation {\noffset: 4,\n};\n
ret ; Return\nret 4 ; Return and add 4 to stack pointer\n
ret ; Return\nadd sp, sp, #4 ; Add 4 to stack pointer\nret ; Return\n
ret ; Return\nret 4 ; Return and add 4 to stack pointer\n
"},{"location":"dev/arch/operations/#architecture-specific-operations","title":"Architecture Specific Operations","text":"

These operations are only available on certain architectures.

These are non essential, but can improve compatibility/performance.

Enabled by setting JitCapabilities::CanEncodeIPRelativeCall and JitCapabilities::CanEncodeIPRelativeJump in JIT.

"},{"location":"dev/arch/operations/#calliprelative","title":"CallIpRelative","text":"

Represents calling an IP-relative offset where target address is stored.

Rustx64ARM64 (+- 1MB)ARM64 (+- 4GB)
let call_rip_rel = CallIpRelativeOperation {\ntarget_address: 0x1000,\n};\n
call qword [rip - 16] ; Address 0x1000 is at RIP-16 and contains raw address to call\n
ldr x9, 4 ; Read item in a multiple of 4 bytes relative to PC\nblr x9    ; Branch call to location\n
adrp x9, #0x0        ; Load 4K page, relative to PC. (round address down to 4096)\nldr x9, [x9, 1110]   ; Read address from offset in 4K page.\nblr x9               ; Branch to location\n
"},{"location":"dev/arch/operations/#jumpiprelative","title":"JumpIpRelative","text":"

Represents jumping to an IP-relative offset where target address is stored.

Rustx64ARM64 (+- 1MB)ARM64 (+- 4GB)
let jump_rip_rel = JumpIpRelativeOperation {\ntarget_address: 0x1000,\n};\n
jmp qword [rip - 16] ; Address 0x1000 is at RIP-16 and contains raw address to jump\n
ldr x9, 4 ; Read item in a multiple of 4 bytes relative to PC\nbr x9     ; Branch call to location\n
adrp x9, #0x0        ; Load 4K page, relative to PC. (round address down to 4096)\nldr x9, [x9, 1110]   ; Read address from offset in 4K page.\nbr x9                ; Branch call to location\n
"},{"location":"dev/arch/operations/#optimized-pushpop-operations","title":"Optimized Push/Pop Operations","text":"

Enabled by setting JitCapabilities::CanMultiPush in JIT.

"},{"location":"dev/arch/operations/#multipush","title":"MultiPush","text":"

Represents pushing multiple registers onto the stack.

Implementations must support push/pop of mixed registers (e.g. Reg+Vector).

Rustx64ARM64x86
let multi_push = MultiPushOperation {\nregisters: [\nPushOperation { register: rbx },\nPushOperation { register: rax },\nPushOperation { register: rcx },\nPushOperation { register: rdx },\n],\n};\n
push rbx\npush rax\npush rcx\npush rdx ; Push rbx, rax, rcx, rdx onto the stack\n
sub sp, sp, #32 ; Decrement stack pointer by 32 bytes  \nstp x9, x8, [sp] ; Store x9 and x8 on the stack\nstp x11, x10, [sp, #16] ; Store x11 and x10 on the stack  \n
push ebx\npush eax\npush ecx\npush edx ; Push ebx, eax, ecx, edx onto the stack\n
"},{"location":"dev/arch/operations/#multipop","title":"MultiPop","text":"

Represents popping multiple registers from the stack.

Implementations must support push/pop of mixed registers (e.g. Reg+Vector).

Rustx64ARM64x86
let multi_pop = MultiPopOperation {\nregisters: [\nPopOperation { register: rdx },\nPopOperation { register: rcx },\nPopOperation { register: rax },\nPopOperation { register: rbx },\n],\n};\n
pop rdx\npop rcx\npop rax\npop rbx ; Pop rdx, rcx, rax, rbx from the stack\n
ldp x11, x10, [sp], #16 ; Load x11 and x10 from stack and update stack pointer\nldp x9, x8, [sp], #16 ; Load x9 and x8 from stack and update stack pointer\n
pop edx\npop ecx\npop eax\npop ebx ; Pop edx, ecx, eax, ebx from the stack\n
"},{"location":"dev/arch/overview/","title":"Architecture Overview","text":"

Lists currently supported architectures and their features.

"},{"location":"dev/arch/overview/#feature-support","title":"Feature Support","text":"

Lists the currently available library features for different architectures.

Feature x86 & x64 ARM64 Basic Function Hooking \u2705 \u2705 Code Relocation \u2705* \u2705 Hook Stacking \u2705 \u2705 Calling Convention Wrapper Generation \u2705 \u2705 Optimal Wrapper Generation \u2705 \u2705 Length Disassembler \u2705 \u2705
  • x86 should work in all cases, but x64 isn't tested against all 5000+ instructions.
"},{"location":"dev/arch/overview/#required","title":"Required","text":""},{"location":"dev/arch/overview/#basic-function-hooking","title":"Basic Function Hooking","text":"

The ability to hook/detour existing application functions.

"},{"location":"dev/arch/overview/#how-to-implement","title":"How to Implement","text":"

Implement a code writer by inheriting the Jit<TRegister> trait

In the writer, implement at least the following operations:

  • JumpRelativeOperation.
  • JumpAbsoluteOperation [needed if platform doesn't support Targeted Memory Allocation].

Your Platform must also support Permission Change, if it is applicable to your platform.

"},{"location":"dev/arch/overview/#length-disassembler","title":"Length Disassembler","text":"

Length disassembly is the ability to determine instruction lengths at a given address.

A length disassembler determines the minimum amount of instructions (in bytes) needed to copy when hooking a function.

/// Disassembles items at `code_address` until the length of instructions\n/// is equal to or greater than `min_length`. \n/// \n/// # Returns\n/// Returns length of instructions (in bytes) greater than or equal to min_length\nfn disassemble_length(code_address: usize, min_length: usize) -> usize\n

This is done by disassembling the original instructions at code_address, incrementing a length for each encountered instruction until length >= min_length, then returning the result.

"},{"location":"dev/arch/overview/#example","title":"Example","text":"

For hooking functions, it's necessary to inject a jmp instruction into the existing code.

For example, given this sequence:

; x86 Assembly\nDoMathWithTwoNumbers:\ncmp rcx, 0 ; 48 83 F9 00\njg skipAdd ; 7F 0E\n\nmov rax, [rsp + 8] ; 48 8B 44 24 04\nmov rax, [rsp + 16] ; 48 8B 4C 24 04\nadd rax, rcx ; 48 01 C8\nret ; C3\n

A `5 byte`` relative jump would overwrite the first two instructions, creating:

; x86 Assembly\nDoMathWithTwoNumbers:\njmp stub ; E9 XX XX XX XX\n<INVALID INSTRUCTION> ; 0E\n\nmov rax, [rsp + 8] ; 48 8B 44 24 04\nmov rax, [rsp + 16] ; 48 8B 4C 24 04\nadd rax, rcx ; 48 01 C8\nret ; C3\n

When calling the original function again, and thus creating the Reverse Wrapper, the original instructions overwritten by the jmp will need to be executed.

To do this, we must know that the original 2 instructions at DoMathWithTwoNumbers were 6, NOT 5 bytes in length total. Such that when we copy the original code to Reverse Wrapper we get

cmp rcx, 0 ; 48 83 F9 00\njg skipAdd ; 7F 0E\n

and not

cmp rcx, 0 ; 48 83 F9 00\n<INVALID INSTRUCTION> ; 7F\n

With a length disassembler, we are able to safely copy all the bytes needed.

"},{"location":"dev/arch/overview/#how-to-implement_1","title":"How to Implement","text":"

Implement a length disassembler by inheriting the LengthDisassembler trait.

Use the algorithm described in example.

"},{"location":"dev/arch/overview/#code-relocation","title":"Code Relocation","text":"

Code relocation is the ability to rewrite existing code such that existing instructions using PC/IP relative operands still have valid operands post patching.

Suppose the following x86 code, which was optimised away to accept first parameter in ecx register:

int DoMathWithTwoNumbers(int operation@ecx, int a, int b) {\n\nif (operation <= 0) {\nreturn a + b;\n}\n\n// Omitted Code Here\n}\n

In this case it's possible that there's a jump in the very beginning of the function:

DoMathWithTwoNumbers:\ncmp ecx, 0\njg skipAdd # It's greater than 0\n\nmov eax, [esp + {wordSize * 1}] ; Left Parameter\nmov ecx, [esp + {wordSize * 2}] ; Right Parameter\nadd eax, ecx\nret\n\n; Some Omitted Code Here\n\nskipAdd:\n; Omitted Code Here\n

In a scenario like this, the hooking library would overwrite the cmp and jg instruction when it assembles the hook entry ('enter hook'); and when the original function is called again by your hook the, 'wrapper' would now contain this jg instruction.

Because jg is an instruction relative to the current instruction address, the library must be able to patch and 'relocate' the function to a new address.

Basic code relocation support is needed to stack hooks.

"},{"location":"dev/arch/overview/#how-to-implement_2","title":"How to Implement","text":"

Implement a relocator by CodeRewriter trait.

There is no 'general strategy' for this, however, here are some pieces of advice:

  • Consider looking at the docs for existing relocators (for RISC, ARM64) is a good reference.
  • You will need to rewrite all control flow instructions (branch etc.)
  • You will need to rewrite all instructions which are relative to current Instruction Pointer/Program Counter.
  • Use disassembler library (if one exists) for your architecture.
"},{"location":"dev/arch/overview/#optional-extras","title":"Optional (Extras)","text":""},{"location":"dev/arch/overview/#calling-convention-wrapper-generation","title":"Calling Convention Wrapper Generation","text":"

The ability to convert between different calling conventions (e.g. cdecl -> stdcall).

To implement this, you implement a code writer by inheriting the Jit<TRegister> trait; and implement the following operations:

  • All Non-Optional Instructions.
"},{"location":"dev/arch/overview/#optimized-wrapper-generation","title":"Optimized Wrapper Generation","text":"

If this is checked, it means the wrappers generate optimal code (to best of knowledge).

While the wrapper generator does most optimisations themselves, in some cases, it may be possible to perform additional optimisations in the JIT/Code Writer side.

For example, the reloaded-hooks wrapper generator might generate the following sequence of pushes for ARM64:

push x0\npush x1\n

A clever ARM64 compiler however would be able to translate this to:

stp x0, x1, [sp, #-16]!\n

For some built in optimisations, like this, you can opt into these specialised instructions with JitCapabilities on your Jit<TRegister>.

Some others, may be implemented at Jit level instead.

"},{"location":"dev/arch/overview/#misc","title":"Misc","text":""},{"location":"dev/arch/overview/#hook-stacking","title":"Hook Stacking","text":"

Hook stacking is the ability to hook a function multiple times.

This should work flawlessly out of the box if all of the required elements are implemented.

"},{"location":"dev/arch/arm64/aarch64/","title":"ARM64","text":"

This is just a quick reference sheet for developers.

ARM64 is not currently implemented.

  • Code Alignment: 4 bytes
"},{"location":"dev/arch/arm64/aarch64/#registers","title":"Registers","text":"Register ARM64 (System V) Volatile/Non-Volatile x0-x7 Parameter/Result Registers Volatile x8 Indirect result location register Volatile x9-x15 Local Variables Volatile x16-x17 Intra-procedure-call scratch registers Volatile x18 Platform register, conventionally the TLS base Volatile x19-x28 Registers saved across function calls Non-Volatile x29 Frame pointer Non-Volatile x30 Link register Volatile sp Stack pointer Non-Volatile xzr Zero register, always reads as zero N/A x31 Stack pointer or zero register, contextually reads as either sp or xzr N/A

For floating point / SIMD registers:

Register ARM64 (System V) Volatile/Non-Volatile v0-v7 Parameter/Result registers Volatile v8-v15 Temporary registers Volatile v16-v31 Registers saved across function calls Non-Volatile"},{"location":"dev/arch/arm64/aarch64/#calling-convention-inference","title":"Calling Convention Inference","text":"

It is recommended library users manually specify conventions in their hook functions.\"

When the calling convention of <your function> is not specified, wrapper libraries must insert the appropriate default convention in their wrappers.

"},{"location":"dev/arch/arm64/aarch64/#rust","title":"Rust","text":"
  • aarch64-unknown-linux-gnu: SystemV
  • aarch64-pc-windows-msvc: Windows ARM64
"},{"location":"dev/arch/arm64/aarch64/#c","title":"C","text":"
  • Linux ARM64: SystemV
  • Windows ARM64: Windows ARM64
"},{"location":"dev/arch/arm64/code_relocation/","title":"Code Relocation","text":"

This page provides a listing of all instructions rewritten as part of the Code Relocation process.

"},{"location":"dev/arch/arm64/code_relocation/#adrp","title":"ADR(P)","text":"

Purpose:

The ADR instruction in ARM architectures computes the address of a label and writes it to the destination register.

Behaviour:

The ADR(P) instruction is rewritten as one of the following: - ADR(P) - ADR(P) + ADD - MOV (1-4 instructions)

Example:

  1. From ADRP to ADR:

    // Before: ADRP x0, 0x101000\n// After: ADR x0, 0xFFFFF\n// Parameters: (old_instruction, old_address, new_address)\nrewrite_adr(0x000800B0_u32.to_be(), 0, 4097);\n

  2. Within 4GiB Range with Offset:

    // Before: ADRP x0, 0x101000\n// After: \n//  - ADRP x0, 0x102000\n//  - ADD x0, x0, 1\nrewrite_adr(0x000800B0_u32.to_be(), 4097, 0);\n

  3. Within 4GiB Range without Offset:

    // Before: ADRP x0, 0x101000\n// After: ADRP x0, 0x102000\nrewrite_adr(0x000800B0_u32.to_be(), 4096, 0);\n

  4. Out of Range:

    // PC = 0x100000000\n\n// Before: ADRP, x0, 0x101000\n// After: MOV IMMEDIATE 0x100101000\nrewrite_adr(0x000800B0_u32.to_be(), 0x100000000, 0);\n

"},{"location":"dev/arch/arm64/code_relocation/#branch-conditional","title":"Branch (Conditional)","text":"

Purpose: The Bcc instruction in ARM architectures performs a conditional branch based on specific condition flags.

Behaviour: The Branch Conditional instruction is rewritten as: - BCC - BCC + [B] - BCC + [ADRP + ADD + BR] - BCC + [MOV to Register + Branch Register]

<skip> means, invert the condition, and jump over the code inside [] brackets.

Example:

  1. Within 1MiB:

    // Before: b.eq #4\n// After: b.eq #-4092\n// Parameters: (old_instruction, old_address, new_address, scratch_register)\nrewrite_bcc(0x20000054_u32.to_be(), 0, 4096, Some(17));\n

  2. Within 128MiB:

    // Before: b.eq #0\n// After: \n//   - b.ne #8 \n//   - b #-0x80000000\nrewrite_bcc(0x00000054_u32.to_be(), 0, 0x8000000 - 4, Some(17));\n

  3. Within 4GiB Range with Address Adjustment:

    // Before: b.eq #512\n// After: \n//   - b.ne #16 \n//   - adrp x17, #0x8000000\n//   - add x17, #512\n//   - br x17\nrewrite_bcc(0x00100054_u32.to_be(), 0x8000000, 0, Some(17));\n

  4. Within 4GiB Range without Offset:

    // Before: b.eq #512\n// After: \n//   - b.ne #12\n//   - adrp x17, #-0x8000000 \n//   - br x17\nrewrite_bcc(0x00100054_u32.to_be(), 0, 0x8000000, Some(17));\n

  5. Last Resort:

    // Before: b.eq #0\n// After: \n//   - b.ne #12\n//   - movz x17, #0 \n//   - br x17\nrewrite_bcc(0x00000054_u32.to_be(), 0, 0x100000000, Some(17));\n

"},{"location":"dev/arch/arm64/code_relocation/#branch","title":"Branch","text":"

Including Branch+Link (BL).

Purpose: The B (or BL for Branch+Link) instruction in ARM architectures performs a direct branch (or branch with link) to a specified address. When using the BL variant, the return address (the address of the instruction following the branch) is stored in the link register LR.

Behaviour: The Branch instruction is rewritten as one of the following: - B (or BL) - ADRP + BR - ADRP + ADD + BR - MOV + BR

Example:

  1. Direct Branch within Range:

    // Before: b #4096\n// After: b #8192\n// Parameters: (old_instruction, old_address, new_address, scratch_register, link)\nrewrite_b(0x00040014_u32.to_be(), 8192, 4096, Some(17), false);\n

  2. Within 4GiB with Address Adjustment:

    // Before: b #4096\n// After: \n//   - adrp x17, #0x8000000\n//   - br x17\nrewrite_b(0x00040014_u32.to_be(), 0x8000000, 0, Some(17), false);\n

  3. Within 4GiB Range with Offset:

    // Before: b #4096\n// After: \n//   - adrp x17, #0x8000512\n//   - add x17, x17, #512\n//   - br x17\nrewrite_b(0x00040014_u32.to_be(), 0x8000512, 0, Some(17), false);\n

  4. Out of Range, Use MOV:

    // Before: b #4096\n// After: \n//   - movz x17, #... \n//   - ...\n//   - br x17\nrewrite_b(0x00040014_u32.to_be(), 0x100000000, 0, Some(17), false);\n

  5. Branch with Link within Range:

    // Before: bl #4096\n// After: bl #8192\nrewrite_b(0x00040094_u32.to_be(), 8192, 4096, Some(17), true);\n

"},{"location":"dev/arch/arm64/code_relocation/#cbz-compare-and-branch-on-zero","title":"CBZ (Compare and Branch on Zero)","text":"

Purpose: The CBZ instruction in ARM architectures performs a conditional branch when the specified register is zero. If the register is not zero and the condition is not met, the next sequential instruction is executed.

Behaviour: The CBZ instruction is rewritten as one of the following: - CBZ - CBZ + [B] - CBZ + [ADRP + BR] - CBZ + [ADRP + ADD + BR] - CBZ + [MOV to Register + Branch Register]

Here, <skip> is used to invert the condition and jump over the set of instructions inside the [] brackets if the condition is not met.

Example:

  1. Within 1MiB Range:

    // Before: cbz x0, #4096\n// After: cbz x0, #8192\n// Parameters: (old_instruction, old_address, new_address)\nrewrite_cbz(0x008000B4_u32.to_be(), 8192, 4096, Some(17));\n

  2. Within 128MiB Range:

    // Before: cbz x0, #4096\n// After: \n//   - cbnz x0, #8\n//   - b #0x8000000\nrewrite_cbz(0x008000B4_u32.to_be(), 0x8000000, 4096, Some(17));\n

  3. Within 4GiB + 4096 aligned:

    // Before: cbz x0, #4096\n// After: \n//   - cbnz x0, <skip 3 instructions> \n//   - adrp x17, #0x8000000\n//   - br x17\nrewrite_cbz(0x008000B4_u32.to_be(), 0x8000000, 0, Some(17));\n

  4. Within 4GiB with Offset:

    // Before: cbz x0, #4096\n// After: \n//   - cbnz x0, <skip 4 instructions>\n//   - adrp x17, #0x8000000\n//   - add x17, #512\n//   - br x17\nrewrite_cbz(0x008000B4_u32.to_be(), 0x8000512, 0, Some(17));\n

  5. Out of Range (Move and Branch):

    // Before: cbz x0, #4096\n// After: \n//   - cbnz x0, <skip X instructions> \n//   - mov x17, <immediate address>\n//   - br x17\nrewrite_cbz(0x008000B4_u32.to_be(), 0x100000000, 0, Some(17));\n

"},{"location":"dev/arch/arm64/code_relocation/#ldr-load-register","title":"LDR (Load Register)","text":"

This includes Prefetch PRFM which shares opcode with LDR.

Purpose: The LDR instruction in ARM architectures is used to load a value from memory into a register. It can use various addressing modes, but commonly it involves an offset from a base register or the program counter.

Behaviour: The LDR instruction is rewritten as one of the following, depending on the relocation range:

  • LDR Literal
  • ADRP + LDR (with Unsigned Offset)
  • MOV Address to Register + LDR

The choice of rewriting strategy is based on the distance between the old address and the new one, with a preference for the most direct form that satisfies the required address range.

If the instruction is Prefetch PRFM, it is discarded if it can't be re-encoded as PRFM (literal), as prefetching with multiple instructions is probably less efficient than not prefetching at all.

Example:

  1. Within 1MiB Range:

    // Before: LDR x0, #0\n// After: LDR x0, #4096\n// Parameters: (opcode, new_imm12, rn)\nrewrite_ldr_literal(0x00000058_u32.to_be(), 4096, 0);\n

  2. Within 4GiB + 4096 aligned:

    // Before: LDR x0, #0\n// After: \n//   - adrp x0, #0x100000\n//   - ldr x0, [x0]\n// Parameters: (opcode, new_address, old_address)\nrewrite_ldr_literal(0x00000058_u32.to_be(), 0x100000, 0);\n

  3. Within 4GiB:

    // Before: LDR x0, #512\n// After: \n//   - adrp x0, #0x100000\n//   - ldr x0, [x0, #512]\n// Parameters: (opcode, new_address, old_address)\nrewrite_ldr_literal(0x00100058_u32.to_be(), 0x100000, 0);\n

  4. Out of Range (Last Resort):

    // Before: LDR x0, #512\n// After: \n//   - movz x0, #0, lsl #16\n//   - movk x0, #0x1, lsl #32\n//   - ldr x0, [x0, #512]\n// Parameters: (opcode, new_address, old_address)\nrewrite_ldr_literal(0x00100058_u32.to_be(), 0x100000000, 0);\n

"},{"location":"dev/arch/arm64/code_relocation/#tbz-test-and-branch-on-zero","title":"TBZ (Test and Branch on Zero)","text":"

Purpose: The TBZ instruction in ARM architectures tests a specified bit in a register and performs a conditional branch if the bit is zero. If the tested bit is not zero, the next sequential instruction is executed.

Behaviour: The TBZ instruction is rewritten based on the distance to the new branch target. It is transformed into one of the following patterns: - TBZ - TBZ + B - TBZ + ADRP + BR - TBZ + ADRP + ADD + BR - TBZ + MOV to Register + Branch Register

Here, <skip> is used to indicate a conditional skip over a set of instructions if the tested bit is not zero. The specific transformation depends on the offset between the current position and the new branch target.

Safety: It is crucial to ensure that the provided instruction parameter is a valid TBZ opcode. Incorrect opcodes or assumptions that a different type of instruction is a TBZ may lead to undefined behaviour.

Functionality: The rewrite_tbz function alters the TBZ instruction to accommodate a new target address that is outside of its original range. The target address could be within the same 32KiB range or farther, necessitating different rewriting strategies.

Example:

  1. Within 32KiB Range:

    // Original: tbz x0, #0, #4096\n// Rewritten: tbz x0, #0, #8192\n// Parameters: (old_instruction, old_address, new_address, scratch_reg)\nrewrite_tbz(0x00800036_u32.to_be(), 8192, 4096, Some(17));\n

  2. Within 128MiB Range:

    // Original: tbz x0, #0, #4096\n// Rewritten:\n//   - tbnz x0, #0, #8\n//   - b #0x8000000\nrewrite_tbz(0x00800036_u32.to_be(), 0x8000000, 4096, Some(17));\n

  3. Within 4GiB Range Aligned to 4096:

    // Original: tbz x0, #0, #4096\n// Rewritten:\n//   - tbnz w0, #0, #0xc\n//   - adrp x17, #0x8001000\n//   - br x17\nrewrite_tbz(0x00800036_u32.to_be(), 0x8000000, 0, Some(17));\n

  4. Within 4GiB Range with Offset:

    // Original: tbz x0, #0, #4096\n// Rewritten:\n//    - tbnz w0, #0, #0x10\n//    - adrp x17, #0x8001000\n//    - add x17, x17, #0x512\n//    - br x17\nrewrite_tbz(0x00800036_u32.to_be(), 0x8000512, 0, Some(17));\n

  5. Out of 4GiB Range (Move and Branch):

    // Original: tbz x0, #0, #4096\n// Rewritten:\n//    - tbnz w0, #0, #0x14\n//    - movz x17, #0x1000\n//    - movk x17, #0, lsl #16\n//    - movk x17, #0x1, lsl #32\n//    - br x17\nrewrite_tbz(0x00800036_u32.to_be(), 0x100000000, 0, Some(17));\n

"},{"location":"dev/arch/x86/code_relocation/","title":"Code Relocation","text":"

This page provides a listing of all instructions rewritten as part of the Code Relocation process for x86 architecture.

This page provides a comprehensive overview of the instruction rewriting techniques used in the code relocation process, specifically tailored for the x64 architecture.

"},{"location":"dev/arch/x86/code_relocation/#any-instruction-within-2gib-range","title":"Any Instruction within 2GiB Range","text":"

If the new relative branch target is within the encodable range, it is left as relative.

"},{"location":"dev/arch/x86/code_relocation/#example-within-relative-range","title":"Example: Within Relative Range","text":"

Original: (EB 02) - jmp +2

Relocated: (E9 FF 0F 00 00) - jmp +4098

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n`#[case::simple_branch(\"eb02\", 4096, 0, \"e9ff0f0000\")]\n

In x86, any address is reachable from any address

This is due to integer over/underflow and immediates being 2GiB in size. Therefore relocation simply involves extending the immediate as needed, i.e. jmp 0x12 to jmp 0x123012 etc.

The rest of the page will therefore leave out relative cases, and only focus on offsets greater than 2GiB.

"},{"location":"dev/arch/x86/code_relocation/#x64-rewriter-going-beyond-the-2gib-offset","title":"x64 Rewriter: Going Beyond the 2GiB Offset","text":"

The x64 rewriter is only suitable for rewriting function prologues.

To be able to perform a lot of actions in a position independent manner, this rewriter uses a dummy 'scratch' register which it will overwrite.

Scratch register is determined by the following logic:

  • Start with Caller Saved Registers (these restored after function call).
  • Remove all registers used in code being rewritten.

Because rewriting a lot of code will lead to register exhaustion, it must be reiterated the rewriter can only be used for small bits of code.

x64 has over 5000 \u203c\ufe0f instructions that require rewriting. Only a couple hundred are tested currently

"},{"location":"dev/arch/x86/code_relocation/#relative-branches","title":"Relative Branches","text":"

Instructions such as JMP, CALL, etc.

Behaviour:

If out of range, it is rewritten using a combination of MOV (move the absolute address into a register) followed by JMP or CALL to that register.

"},{"location":"dev/arch/x86/code_relocation/#example","title":"Example","text":"

Original: (EB 02) - jmp +2

Relocated: (48 B8 04 00 00 80 00 00 00 00 FF E0) - mov rax, 0x80000004 - jmp rax

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n#[case::to_abs_jmp_i8(\"eb02\", 0x80000000, 0, \"48b80400008000000000ffe0\")]\n
"},{"location":"dev/arch/x86/code_relocation/#jump-conditional","title":"Jump Conditional","text":"

Instructions such as jne, jg etc.

Behaviour:

  • Inverts the branch condition, then jumps over an absolute jump that is encoded using a MOV to set the address and a JMP to that address.
"},{"location":"dev/arch/x86/code_relocation/#example_1","title":"Example","text":"

Example:

Original: (70 02) - jo +2

Relocated: (71 0C 48 B8 04 00 00 80 00 00 00 FF E0): - jno +12 <skip> - mov rax, 0x80000004 - jmp rax

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n#[case::jo(\"7002\", 0x80000000, 0, \"710c48b80400008000000000ffe0\")]\n
"},{"location":"dev/arch/x86/code_relocation/#loop-instructions","title":"Loop Instructions","text":"

Instructions such as LOOP, LOOPE, and LOOPNE.

Behaviour:

Handled by either:

  • Manually decrementing ECX and using a conditional jump based on the zero flag. (i.e. extend 'loop' address to 32-bit)

or

  • Branching the loop function in the opposite direction.

The strategy used depends on the original instruction.

"},{"location":"dev/arch/x86/code_relocation/#example-branch-in-opposite-direction","title":"Example: Branch in Opposite Direction","text":"

Original: (E2 FA) - loop -3

Relocated: (50 E2 02 EB 0C 48 B8 FD 0F 00 80 00 00 00 00 FF E0) - push rax - loop +2 - jmp 0x11 - movabs rax, 0x80000ffd - jmp rax

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n#[case::loop_backward_abs(\"50e2fa\", 0x80001000, 0, \"50e202eb0c48b8fd0f008000000000ffe0\")]\n
"},{"location":"dev/arch/x86/code_relocation/#jcx-instructions","title":"JCX Instructions","text":"

Instructions such as JCXZ, JECXZ, JRCXZ.

Behaviour:

  • If the target is within 32-bit range, it uses an optimized IMM32 encoding.
  • If out of 32-bit range, it uses a TEST instruction followed by a conditional jump.
"},{"location":"dev/arch/x86/code_relocation/#example_2","title":"Example","text":"

Original: (E3 FA) - jrcxz -3

Relocated: (E3 02 EB 0C 48 B8 FD 0F 00 80 00 00 00 00 FF E0) - jrcxz +5 - jmp 0x11 - mov rax, 0x80000ffd - jmp rax

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n#[case::jrcxz_abs(\"e3fa\", 0x80001000, 0, \"e302eb0c48b8fd0f008000000000ffe0\")]\n
"},{"location":"dev/arch/x86/code_relocation/#rip-relative-operand","title":"RIP Relative Operand","text":"

At time of writing, this covers around 2800 \u203c\ufe0f instructions

Only around a 100 are covered by unit tests though.

Covers all instructions which have an IP relative operand, i.e. read/write to a memory address which is relative to the address of the next instruction.

Behaviour:

Replace RIP relative operand with a scratch register with the originally intended memory address.

"},{"location":"dev/arch/x86/code_relocation/#example_3","title":"Example","text":"

Original: (48 8B 1D 08 00 00 00) - mov rbx, [rip + 8]

Relocated: (48 B8 0F 00 00 00 01 00 00 00 48 8B 18) - mov rax, 0x10000000f - mov rbx, [rax]

// Parameters for test case:\n// - Original Code (Hex)\n// - Original Address\n// - New Address\n// - New Expected Code (Hex)\n#[case::mov_rhs(\"488b1d08000000\", 0x100000000, 0, \"48b80f00000001000000488b18\")]\n
"},{"location":"dev/arch/x86/code_relocation/#how-this-is-done","title":"How this is Done","text":"

reloaded-hooks-rs uses the iced library under the hood for assembly and disassembly.

In iced, operands can be broken down to 3 main types:

Name Note register Including Vector Registers memory i.e. [rax] or [rip + 4] imm Immediate, 8/16/32/64

Immediates use multiple types, e.g. Immediate8, Immediate16 etc. but on assembler side you can pass them all as Immediate32, so you can group them.

Each instruction can have 0-5 operands, where there is at max 1 operand which can be RIP relative.

To handle this, a script projects/code-generators/x86/generate_enum_ins_combos.py was used to dump all possible operand permutations from Iced source. Then I wrote functions to handle each possible permutation.

1 Operand:

  • rip

2 Operands:

  • rip, imm
  • rip, reg
  • reg, rip

3 Operands:

  • reg, reg, rip
  • reg, rip, imm
  • rip, reg, imm
  • rip, reg, reg
  • reg, rip, reg

4 Operands:

  • reg, reg, rip, imm
  • reg, reg, reg, rip

5 Operands:

  • reg, reg, reg, rip, imm
  • reg, reg, rip, reg, imm

If reloaded-hooks-rs encounters an instruction with RIP relative operand that uses any of the following operand permutations, it should successfully patch it.

"},{"location":"dev/arch/x86/x86/","title":"x86","text":"

This is just a quick reference sheet for developers.

  • Code Alignment: 16 bytes
"},{"location":"dev/arch/x86/x86/#registers","title":"Registers","text":"Register stdcall (Microsoft x86) cdecl eax Caller-saved, return value Caller-saved, return value ebx Callee-saved Callee-saved ecx Caller-saved Caller-saved edx Caller-saved Caller-saved esi Callee-saved Callee-saved edi Callee-saved Callee-saved ebp Callee-saved Callee-saved esp Callee-saved Callee-saved

For floating point registers:

Register stdcall (Microsoft x86) cdecl st(0)-st(7) Caller-saved, st(0) used for returning floating point values. Caller-saved, st(0) used for returning floating point values. mm0-mm7 Caller-saved Caller-saved xmm0-xmm7 Caller-saved Caller-saved

Both calling conventions pass function parameters on the stack, in right-to-left order, and they both return values in eax. For floating-point values or larger structures, the FPU stack or additional conventions are used. The main difference for function calls is that stdcall expects the function (callee) to clean up the stack, while cdecl expects the caller to do it.

"},{"location":"dev/arch/x86/x86/#calling-convention-inference","title":"Calling Convention Inference","text":"

It is recommended library users manually specify conventions in their hook functions.\"

When the calling convention of <your function> is not specified, wrapper libraries must insert the appropriate default convention in their wrappers.

"},{"location":"dev/arch/x86/x86/#rust","title":"Rust","text":"
  • i686-pc-windows-gnu: cdecl
  • i686-pc-windows-msvc: cdecl
  • i686-unknown-linux-gnu: SystemV
"},{"location":"dev/arch/x86/x86/#c","title":"C","text":"
  • Linux x86: SystemV
  • Windows x86: cdecl
"},{"location":"dev/arch/x86/x86_64/","title":"x86_64","text":"

This is just a quick reference sheet for developers.

  • Code Alignment: 16 bytes
"},{"location":"dev/arch/x86/x86_64/#registers","title":"Registers","text":"

The order of the registers is typically as follows for Microsoft x64 ABI: rcx, rdx, r8, r9, then the rest of the parameters are pushed onto the stack in reverse order (right-to-left).

For the System V ABI on x64: rdi, rsi, rdx, rcx, r8, r9, then the rest of the parameters are pushed onto the stack in reverse order (right-to-left).

Register Microsoft x64 ABI SystemV ABI rax Caller-saved Caller-saved rbx Callee-saved Callee-saved rcx Caller-saved, 1st parameter Caller-saved, 4th parameter rdx Caller-saved, 2nd parameter Caller-saved, 3rd parameter rsi Caller-saved Caller-saved, 2nd parameter rdi Caller-saved Caller-saved, 1st parameter rbp Callee-saved Callee-saved rsp Callee-saved Callee-saved r8 Caller-saved, 3rd parameter Caller-saved, 5th parameter r9 Caller-saved, 4th parameter Caller-saved, 6th parameter r10 Caller-saved Caller-saved r11 Caller-saved Caller-saved r12 Callee-saved Callee-saved r13 Callee-saved Callee-saved r14 Callee-saved Callee-saved r15 Callee-saved Callee-saved

Floating Point Registers (Microsoft)

Register Microsoft x64 ABI st(0)-st(7) Caller-saved mm0-mm7 Caller-saved xmm0-xmm5 Caller-saved, used for floating point parameters. ymm0-zmm5 Caller-saved, used for floating point parameters. zmm0-zmm5 Caller-saved, used for floating point parameters. xmm6-xmm15 Callee-saved. ymm6-ymm15 Callee-saved. Upper half must be preserved by the caller zmm6-zmm31 Callee-saved. Upper half must be preserved by the caller

Floating Point Registers (SystemV)

Register SystemV ABI st(0)-st(7) Caller-saved mm0-mm7 Caller-saved xmm0-xmm7 Caller-saved, used for floating point parameters ymm0-zmm7 Caller-saved, used for floating point parameters zmm0-zmm7 Caller-saved, used for floating point parameters xmm8-xmm15 Caller-saved ymm8-ymm15 Caller-saved, used for floating point parameters zmm8-zmm31 Caller-saved, used for floating point parameters

On Linux, syscalls use R10 instead of RCX in SystemV ABI

"},{"location":"dev/arch/x86/x86_64/#intel-apx","title":"Intel APX","text":"

Information sourced from Source.

Future Intel processors are expected to ship with APX, extending the registers to 32 by adding R16-R31.

These future registers are expected to be caller saved.

To quote document:

Defining all new state (Intel\u00ae APX\u2019s EGPRs) as volatile (caller-saved or scratch)

"},{"location":"dev/arch/x86/x86_64/#calling-convention-inference","title":"Calling Convention Inference","text":"

It is recommended library users manually specify conventions in their hook functions.\"

When the calling convention of <your function> is not specified, wrapper libraries must insert the appropriate default convention in their wrappers.

"},{"location":"dev/arch/x86/x86_64/#rust","title":"Rust","text":"
  • x86_64-pc-windows-gnu: Microsoft
  • x86_64-pc-windows-msvc: Microsoft
  • x86_64-unknown-linux-gnu: SystemV
  • x86_64-apple-darwin: SystemV
"},{"location":"dev/arch/x86/x86_64/#c","title":"C","text":"
  • Windows x64: Microsoft
  • Linux x64: SystemV
  • macOS x64: SystemV
"},{"location":"dev/design/common/","title":"Common Design Notes","text":"

Design notes common to all hooking strategies.

"},{"location":"dev/design/common/#wrappers","title":"Wrappers","text":""},{"location":"dev/design/common/#wrapper","title":"Wrapper","text":"

Wrappers are stubs which convert from the calling convention of the original function to your calling convention.

If the calling convention of the hooked function and your function matches, this wrapper is simply just 1 jmp instruction.

Wrappers are documented in their own page here.

"},{"location":"dev/design/common/#reversewrapper","title":"ReverseWrapper","text":"

Stub which converts from your code's calling convention to original function's calling convention

This is basically Wrapper with source and destination swapped around

"},{"location":"dev/design/common/#hook-memory-layouts-thread-safety","title":"Hook Memory Layouts & Thread Safety","text":"

Hooks in reloaded-hooks-rs are structured in a very specific way to ensure thread safety.

They sacrifice a bit of memory usage in favour of performance + thread safety.

Most hooks, regardless of type have a memory layout that looks something like this:

// Size: 2 registers\npub struct Hook\n{\n/// The address of the stub containing bridging code\n/// between your code and custom code. This is the address\n/// of the code that will actually be executed at runtime.\nstub_address: usize,\n\n/// Address of the 'properties' structure, containing\n/// the necessary info to manipulate the data at stub_address\nprops: NonNull<StubPackedProps>,\n}\n

Notably, there are two heap allocations. One at stub_address, which contains the executable code, and one at props, which contains packed info of the stub at stub_address.

The hooks use a 'swapping' system. Both stub_address and props contains swap space. When you enable or disable a hook, the data in the two 'swap spaces' are swapped around.

In other words, when stub_address' 'swap space' contains the code for HookFunction (hook enabled), the 'swap space' at props' contains the code for Original Code.

Thread safety is ensured by making writes within the stub itself atomic, as well as making the emplacing of the jump to the stub in the original application code atomic.

"},{"location":"dev/design/common/#stub-layout","title":"Stub Layout","text":"

The memory region containing the actual executed code.

The stub has two possible layouts, if the Swap Space is small enough such that it can be atomically overwritten, it will look like this:

- 'Swap Space' [HookCode / OriginalCode]\n<pad to atomic register size>\n

Otherwise, if Swap Space cannot be atomically overwritten, it will look like:

- 'Swap Space' [HookCode / OriginalCode]\n- HookCode\n- OriginalCode\n

Some hooks may store, extra data after OriginalCode.

For example, if calling convention conversion is needed, the HookCode becomes a ReverseWrapper, and the stub will also contain a Wrapper.

If calling convention conversion is needed, the layout looks like this:

- 'Swap Space' [ReverseWrapper / OriginalCode]\n- ReverseWrapper\n- OriginalCode\n- Wrapper\n
"},{"location":"dev/design/common/#example","title":"Example","text":"

Using ARM64 Assembly Hook as an example.

If the 'OriginalCode' was:

mov x0, x1\nadd x0, x2\n

And the 'HookCode' was:

add x1, x1\nmov x0, x2\n

The memory would look like this when hook is enabled.

swap: ; Currently Applied (Hook)\nmov x0, x1\nadd x0, x2\nb back_to_code\n\nhook: ; HookCode\nadd x1, x1\nmov x0, x2\nb back_to_code\n\noriginal: ; OriginalCode\nmov x0, x1\nadd x0, x2\nb back_to_code\n

(When sizeof(swap) is larger than biggest possible atomic write.)

"},{"location":"dev/design/common/#heap-props-layout","title":"Heap (Props) Layout","text":"

Each Assembly Hook contains a pointer to the heap stub (seen above) and a pointer to the heap.

The heap contains all information required to perform operations on the stub.

- StubPackedProps\n    - Enabled Flag\n    - IsSwapOnly\n    - SwapSize\n    - HookSize\n- [Hook Function / Original Code]\n

The data in the heap contains a short `StubPackedProps`` struct, detailing the data stored over in the stub.

The SwapSize contains the length of the 'swap' info (and also consequently, offset of HookCode). The HookSize contains the length of the 'hook' instructions (and consequently, offset of OriginalCode).

If the IsSwapOnly flag is set, then this data is to be atomically overwritten.

"},{"location":"dev/design/common/#the-enable-disable-process","title":"The 'Enable' / 'Disable' Process","text":"

When transitioning between Enabled/Disabled state, we place a temporary branch at entry, this allows us to manipulate the remaining code safely.

Using ARM64 Assembly Hook as an example.

We start the 'disable' process with a temporary branch:

entry: ; Currently Applied (Hook)\nb original ; Temp branch to original\nmov x0, x2\nb back_to_code\n\nhook: ; Backup (Hook)\nadd x1, x1\nmov x0, x2\nb back_to_code\n\noriginal: ; Backup (Original)\nmov x0, x1\nadd x0, x2\nb back_to_code\n

Don't forget to clear instruction cache on non-x86 architectures which need it.

This ensures we can safely overwrite the remaining code...

Then we overwrite entry code with hook code, except the branch:

entry: ; Currently Applied (Hook)\nb original     ; Branch to original\nadd x0, x2     ; overwritten with 'original' code.\nb back_to_code ; overwritten with 'original' code.\n\nhook: ; Backup (Hook)\nadd x1, x1\nmov x0, x2\nb back_to_code\n\noriginal: ; Backup (Original)\nmov x0, x1\nadd x0, x2\nb back_to_code\n

And lastly, overwrite the branch.

To do this, read the original sizeof(nint) bytes at entry, replace branch bytes with original bytes and do an atomic write. This way, the remaining instruction is safely replaced.

entry: ; Currently Applied (Hook)\nadd x1, x1     ; 'original' code.\nadd x0, x2     ; 'original' code.\nb back_to_code ; 'original' code.\n\noriginal: ; Backup (Original)\nmov x0, x1\nadd x0, x2\nb back_to_code\n\nhook: ; Backup (Hook)\nadd x1, x1\nmov x0, x2\nb back_to_code\n

This way we achieve zero overhead CPU-wise, at expense of some memory.

"},{"location":"dev/design/common/#limits","title":"Limits","text":"

Stub info is packed by default to save on memory space. By default, the following limits apply:

Property 4 Byte Instruction (e.g. ARM64) Other (e.g. x86) Max Orig Code Length 128KiB 32KiB Max Hook Code Length 128KiB 32KiB

These limits may increase in the future if additional required functionality warrants extending metadata length.

"},{"location":"dev/design/common/#thread-safety-on-x86","title":"Thread Safety on x86","text":"

Thread safety is 'theoretically' not guaranteed for every possible x86 processor, however is satisfied for all modern CPUs.

The information below is x86 specific but applies to all architectures with a non-fixed instruction size. Architectures with fixed instruction sizes (e.g. ARM) are thread safe in this library by default.

"},{"location":"dev/design/common/#the-theory","title":"The Theory","text":"

If the jmp instruction emplaced when switching state overwrites what originally were multiple instructions, it is theoretically possible that the placing the jmp will make the instruction about to be executed invalid.

For example if the previous instruction sequence was:

0x0: push ebp\n0x1: mov ebp, esp ; 2 bytes\n

And inserting a jmp produces:

0x0: jmp disabled ; 2 bytes\n

It's possible that the CPU's Instruction Pointer was at 0x1`` at the time of the overwrite, making themov ebp, esp` instruction invalid.

"},{"location":"dev/design/common/#what-happens-in-practice","title":"What Happens in Practice","text":"

In practice, modern x86 CPUs (1990 onwards) from Intel, AMD and VIA prefetch instruction in batches of 16 bytes. We place our stubs generated by the various hooks on 16-byte boundaries for this (and optimisation) reasons.

So, by the time we change the code, the CPU has already prefetched the instructions we are atomically overwriting.

In other words, it is simply not possible to perfectly time a write such that a thread at 0x1 (mov ebp, esp) would read an invalid instruction, as that instruction was prefetched and is being executed from local thread cache.

"},{"location":"dev/design/common/#what-is-safe","title":"What is Safe","text":"

Here is a thread safety table for x86, taking the above into account:

Safe? Hook Notes \u2705 Function Functions start on multiples of 16 on pretty much all compilers, per Intel Optimisation Guide. \u2705 Branch Stubs are 16 aligned. \u2705 Assembly Stubs are 16 aligned. \u2705 VTable VTable entries are usize aligned, and don't cross cache boundaries."},{"location":"dev/design/common/#hook-length-mismatch-problem","title":"Hook Length Mismatch Problem","text":"

When a hook is already present, and you wish to stack that hook over the existing hook, certain problems might arise.

"},{"location":"dev/design/common/#when-your-hook-is-shorter-than-original","title":"When your hook is shorter than original.","text":"

This is notably an issue when a hook entry composes of more than 1 instruction; i.e. on RISC architectures.

There is a potential register allocation caveat in this scenario.

Pretend you have the following ARM64 function:

ARM64C
ADD x1, #5\nADD x2, #10\nADD x0, x1, x2\nADD x0, x0, x0\nRET\n
x1 = x1 + 5;\nx2 = x2 + 10;\nint x0 = x1 + x2;\nx0 = x0 + x0;\nreturn x0;\n

And then, a large hook using an absolute jump with register is applied:

# Original instructions here replaced\nMOVZ x0, A\nMOVK x0, B, LSL #16\nMOVK x0, C, LSL #32\nMOVK x0, D, LSL #48\nB x0\n# <= branch returns here\n

If you then try to apply a smaller hook after applying the large hook, you might run into the following situation:

# The 3 instructions here are an absolute jump using pointer.\nadrp x9, [0]        ldr x9, [x9, 0x200] br x9\n# Call to original function returns here, back to then branch to previous hook\nMOVK x0, D, LSL #48\nB x0\n

This is problematic, with respect to register allocation. Absolute jumps on some RISC platforms like ARM will always require the use of a scratch register.

But there is a risk the scratch register used is the same register (x0) as the register used by the previous hook as the scratch register. In which case, the jump target becomes invalid.

"},{"location":"dev/design/common/#resolution-strategy","title":"Resolution Strategy","text":"
  • Prefer absolute jumps without scratch registers (if possible).
  • Detect mov + branch combinations for each target architecture.
    • And extend the function's stolen bytes to cover the entirety.
    • This avoids the scratch register duplication issue, as original hook code will branch to its own code before we end up using the same scratch register.
"},{"location":"dev/design/common/#when-your-hook-is-longer-than-original","title":"When your hook is longer than original.","text":"

Only applies to architectures with variable length instructions. (x86)

Some hooking libraries don't clean up remaining stolen bytes after installing a hook.

Very notably Steam does this for rendering (overlay) and input (controller support).

Consider the original function having the following instructions:

48 8B C4      mov rax, rsp\n48 89 58 08   mov [rax + 08], rbx\n

After Steam hooks, it will leave the function like this

E9 XX XX XX XX    jmp 'somewhere'\n58 08             <invalid instruction. leftover from state before>\n

If you're not able to install a relative hook, e.g. need to use an absolute jump

FF 25 XX XX XX XX    jmp ['addr']\n

The invalid instructions will now become part of the 'stolen' bytes, when you call the original; and invalid instructions may be executed.

"},{"location":"dev/design/common/#resolution-strategy_1","title":"Resolution Strategy","text":"

This library must do the following:

  • Prefer shorter hooks (relative jump over absolute jump) when possible.
  • Leave nop(s) after placing any branches, to avoid leaving invalid instructions.
    • Don't contribute to the problem.

There unfortunately isn't much we can do to detect invalid instructions generated by other hooking libraries reliably, best we can do is try to avoid it by using shorter hooks. Thankfully this is not a common issue given most people use the 'popular' libraries.

"},{"location":"dev/design/common/#fallback-strategies","title":"Fallback Strategies","text":""},{"location":"dev/design/common/#return-address-patching","title":"Return Address Patching","text":"

This feature will not be ported over from legacy Reloaded.Hooks, until an edge case is found that requires this.

This section explains how Reloaded handles an edge case within an already super rare case.

This topic is a bit more complex, so we will use x86 as example here.

For any of this to be necessary, the following conditions must be true:

  • An existing relative jump hook exists.
  • Reloaded can't find free memory within relative jump range.
  • The existing hook was somehow able to find free memory in this range, but we can't... (<= main reason this is improbable!!)
  • Free Space from Function Alignment Strategy fails.
  • The instructions at beginning of the hooked function happened to just perfectly align such that our hook jump is longer than the existing one.

The low probability of this happening, at least on Windows and/or Linux is rather insane. It cannot be estimated, but if I were to have a guess, maybe 1 in 1 billion. You'd be more likely to die from a shark attack.

In any case, when this happens, Reloaded performs return address patching.

Suppose a foreign hooking library hooks a function with the following prologue:

55        push ebp\n89 e5     mov ebp, esp\n00 00     add [eax], al\n83 ec 20  sub esp, 32 ...\n

After hooking, this code would look like:

E9 XX XX XX XX  jmp 'somewhere'\n<= existing hook jumps back here when calling original (this) function\n83 ec 20        sub esp, 32 ...\n

When the prologue is set up 'just right', such that the existing instrucions divide perfectly into 5 bytes, and we need to insert a 6 byte absolute jmp FF 25, Reloaded must patch the return address.

Reloaded has a built in patcher for this super rare scenario, which detects and attempts to patch return addresses of the following patterns:

Where nop* represents 0 or more nops.\n\n1. Relative immediate jumps.       \n\n    nop*\n    jmp 0x123456\n    nop*\n\n2. Push + Return\n\n    nop*\n    push 0x612403\n    ret\n    nop*\n\n3. RIP Relative Addressing (X64)\n\n    nop*\n    JMP [RIP+0]\n    nop*\n

This patching mechanism is rather complicated, relies on disassembling code at runtime and thus won't be explained here.

Different hooking libraries use different logic for storing callbacks. In some cases alignment of code (or rather lack thereof) can also make this operation unreliable, since we rely on disassembling the code at runtime to find jumps back to end of hook. The success rate of this operation is NOT 100%

"},{"location":"dev/design/common/#requirements-for-external-libraries-to-interoperate","title":"Requirements for External Libraries to Interoperate","text":"

While I haven't studied the source code of other hooking libraries before, I've had no issues in the past with the common Detours and minhook libraries that are commonly used

"},{"location":"dev/design/common/#hooking-over-reloaded-hooks","title":"Hooking Over Reloaded Hooks","text":"

Libraries which can safely interoperate (stack hooks ontop) of Reloaded Hooks Hooks' must satisfy the following.

  • Must be able to patch (re-adjust) relative jumps.

    • In some cases when assembling call to original function, relative jump target may be out of range, compatible hooking software must handle this edge case.
  • Must be able to automatically determine number of bytes to steal from original function.

    • This makes it possible to interoperate with the rare times we do a absolute jump when it may not be possible to do a relative jump (i.e.) as we cannot allocate memory in close enough proximity.
"},{"location":"dev/design/common/#reloaded-hooks-hooking-over-existing-hooks","title":"Reloaded Hooks hooking over Existing Hooks","text":"

See: Code Relocation

"},{"location":"dev/design/wrappers/","title":"Calling Conversion Wrappers","text":"

Describes how stubs for converting between different Calling Conventions (ABIs) are generated.

This page uses x86 as an example, however the same concepts apply to other architectures.

These stubs are what allows Reloaded.Hooks-rs to hook functions which take parameters in custom registers, allowing developers to skip writing error prone 'naked' functions by hand.

"},{"location":"dev/design/wrappers/#general-strategy","title":"General Strategy","text":"

Setting frame pointer (ebp) is not necessary, as our wrapper shouldn't use it

  • Backup Non-Volatile Registers (incl. Link Register on Relevant Platforms)
# push LR if present on platform\npush ebp\npush ebx\npush edi\npush esi\n
  • Align the Stack
  • Setup Function Parameters

    • Re push stack parameters (right to left) of the function being returned
    # In a loop\npush dword [ebp + {baseStackOffset}]\n
    • Push register parameters of the function being returned (right to left, reverse loop)
    • Pop parameters into registers of function being called
  • Reserve Extra Stack Space

Some calling conventions require extra space reserved up front

sub esp, {whatever}\n
  • Call Target Method
  • Setup Return Register

If target function returns in different register than caller expects, might need to for example mov eax, ecx.

mov eax, ecx\n
  • Restore Non-Volatile Registers
# Restore non-volatile registers\npop esi\npop edi\npop ebx\npop ebp\n# pop LR if relevant on given platform\n

The general implementation for 64-bit is the same, however the stack must be 16 byte aligned at method entry, and for MSFT convention, 32 bytes reserved on stack before call

There are also some very minor nuances, which the actual code has to handle, but this is the general jist of it.

"},{"location":"dev/design/wrappers/#optimization","title":"Optimization","text":""},{"location":"dev/design/wrappers/#align-wrappers-to-architecture-recommended-alignment","title":"Align Wrappers to Architecture Recommended Alignment","text":"

This optimizes CPU instruction fetch, which (on x86) operates on 16 byte boundaries.

So we align our wrappers to these boundaries.

"},{"location":"dev/design/wrappers/#eliminate-callee-saved-registers","title":"Eliminate Callee Saved Registers","text":"

When there are overlaps in callee saved registers between source and target, we can skip backing up those registers.

For example, cdecl and stdcall use the same callee saved registers, ebp, ebx, esi, edi. When converting between these two conventions, it is not necessary to backup/restore any of them in the wrapper, because the target function will already take care of that.

Example: cdecl target -> stdcall wrapper.

BeforeAfter
# Stack Backup\npush ebp\nmov ebp, esp\n\n# Callee Save\npush ebx\npush edi\npush esi\n\n# Re push parameters\npush dword [ebp + {x}]\npush dword [ebp + {x}]\n\ncall {function}\nadd esp, 8\n\n# Callee Restore\npop esi\npop edi\npop ebx\n\n# Stack Restore\npop ebp\nret 8\n
# Stack Backup\npush ebp\nmov ebp, esp\n\n# Re push parameters\npush dword [ebp + {x}]\npush dword [ebp + {x}]\n\ncall {function}\nadd esp, 8\n\n# Stack Restore\npop ebp\nret 8\n

Pseudocode example. Not verified for accuracy, but it shows the idea

In the cdecl -> stdcall example, ebp is considered a callee saved register too, thus it should be possible to optimise into:

# Re push parameters\npush dword [esp + {x}]\npush dword [esp + {x}]\n\ncall {function}\nadd esp, 8\n\nret 8\n
"},{"location":"dev/design/wrappers/#combine-pushpop-operations-when-possible","title":"Combine Push/Pop Operations when Possible","text":"

When pushing multiple registers at once, it is possible to remove redundant stack operations.

Imagine a situation where you need to push 3 float registers onto the stack; if we pass the instructions from the wrapper generator verbatim [push a, then push b, then push c], we would land with the following:

; Push XMM registers\nsub rsp, 16\nmovdqu [rsp], xmm0\nsub rsp, 16\nmovdqu [rsp], xmm1\nsub rsp, 16\nmovdqu [rsp], xmm2\n\n; Pop XMM registers\nmovdqu xmm2, [rsp]\nadd rsp, 16\nmovdqu xmm1, [rsp]\nadd rsp, 16\nmovdqu xmm0, [rsp]\nadd rsp, 16\n

This is unoptimal as it can be simplified to:

# Push Registers to the Stack\nsub rsp, 48\nmovdqu [rsp], xmm0 movdqu [rsp + 16], xmm1\nmovdqu [rsp + 32], xmm2\n\n# Pop three XMM registers from the Stack\nmovdqu xmm0, [rsp]\nmovdqu xmm1, [rsp + 16]\nmovdqu xmm2, [rsp + 32]\nadd rsp, 48\n

When generating wrappers, the generator must recognise this pattern, and merge multiple push/pop operations into a single block, wherever possible.

It is optimal to access memory sequentially from lowest to highest address.

"},{"location":"dev/design/wrappers/#move-between-registers-instead-of-push-pop","title":"Move Between Registers Instead of Push Pop","text":"

In some cases it's possible to mov between registers, rather than doing an explicit push+pop operation

Suppose you have a custom target -> stdcall wrapper. Custom is defined as int@eax FastAdd(int a@eax, int b@ecx).

Normally wrapper generation will convert the arguments like this:

# Re-push STDCALL arguments to stack\npush dword [esp + {x}]\npush dword [esp + {x}]\n\n# Pop into correct registers\npop eax\npop ecx\n\n# Call that function\n# ...\n

There's opportunities for optimisation here; notably you can do:

# Pop into correct registers\nmov eax, [esp + {x}]\nmov ecx, [esp + {x}]\n\n# Call that function\n# ...\n

Optimising cases where the source/from convention, e.g. custom target -> stdcall has no register parameters is trivial, since you can directly mov into the intended target register. And this is the most common use case in x86.

For completeness, it should be noted that in the opposite direction stdcall target -> custom, such as one that would be used in entry point of a hook (ReverseWrapper), no optimisation is needed here, as all registers are directly pushed without any extra steps.

In the backend, the wrapper generator keeps track of current stack pointer (assuming start is '0'); and uses that information to match the push and pop operations accordingly \ud83d\ude09

"},{"location":"dev/design/wrappers/#with-register-to-register","title":"With Register to Register","text":"

In x64, and more advanced x86 scenarios where both to/from calling convention have register parameters, mov optimisation is not trivial.

"},{"location":"dev/design/wrappers/#basic-case","title":"Basic Case","text":"

Suppose you have a a function to add 'health' to a character that's in a struct or class. i.e. int AddHealth(Player* this, int amount). (Note: The 'this' parameter to struct instance is implicit and added during compilation.)

C++x64 asm (SystemV)x64 asm (Microsoft)
class Player {\nint mana;\nint health;\n\nvoid AddHealth(int amount) {\nhealth += amount;\n}\n};\n
add dword [rdi+4], esi\nret\n

See for yourself.

add dword [rcx+4], edx\nret\n

See for yourself.

If you were to make a SystemV target -> Microsoft wrapper; you would have to move the two registers from rcx, rdx to rdi, rsi.

Therefore, a wrapper might have code that looks something like:

# Push register parameters of the function being returned (right to left, reverse loop)\npush rdx\npush rcx\n\n# Pop parameters into registers of function being called\npop rdi\npop rsi\n

In this case, it is possible to optimise with:

mov rdi, rcx # last push, first pop\nmov rsi, rdx # second last push, second pop\n

Provided that the wrapper correctly saves and restores callee moved registers for returned method, i.e. backs up RBX, RBP, RDI, RSI, RSP, R12, R13, R14, and R15, this is fine.

Or in the case of this wrapper, just RDI, RSI (due to overlap within the 2 conventions).

The 'strategy' to generate code for this optimisation is keeping track of stack, start between push and pop in the ASM and pair the registers in the corresponding push and pop operations together, going outwards until there is no push/pop left.

"},{"location":"dev/design/wrappers/#advanced-case","title":"Advanced Case","text":"

This is just another example.

Suppose we add 2 more parameters...

C++x64 asm (SystemV)x64 asm (Microsoft)
class Player {\nint mana;\nint health;\nint money;\n\nvoid AddStats(int health, int mana, int money) {\nthis->health += health;\nthis->mana += mana;\nthis->money += money;\n}\n};\n
add dword [rdi+4], esi # health\nadd dword [rdi], edx   # mana\nadd dword [rdi+8], ecx # money\nret\n

See for yourself.

add dword [rcx+4], edx # health\nadd dword [rcx], r8d   # mana\nadd dword [rcx+8], r9d # money\nret\n

See for yourself.

There is now an overlap between the registers used.

Microsoft convention uses: - rcx for self - rdx for health

SystemV uses: - rcx for money - rdx for mana

The wrapper now does the following

UnoptimisedOptimised (Contains Bug)
# Push register parameters of the function being returned (right to left, reverse loop)\npush rcx\npush rdx\npush rsi\npush rdi\n\n# Pop parameters into registers of function being called\npop rcx\npop rdx\npop r8\npop r9\n
mov rcx, rdi\nmov rdx, rsi\nmov r8, rdx\nmov r9, rcx\n

The optimised version of code above contains a bug.

There is a bug because both conventions have overlapping registers, notably rcx and rdx. When you try to do mov r8, rdx, this pushes invalid data, as rdx was already overwritten.

In this specific case, you can reverse the order of operations, and get a correct result:

# Reversed\nmov r9, rcx\nmov r8, rdx\nmov rdx, rsi\nmov rcx, rdi\n

However might not always be the case.

When generating wrappers, we must perform a validation check to determine if any source register in mov target, source hasn't already been overwritten by a prior operation.

"},{"location":"dev/design/wrappers/#reordering-operations","title":"Reordering Operations","text":"

In the Advanced Case we saw that it's not always possible to perform mov optimisation.

This problem can be solved with a Directed Acyclic Graph.

This problem can be solved in O(n) complexity with a Directed Acyclic Graph, where each node represents a register and an edge (arrow) from Node A to Node B represents a move from register A to register B.

The above (buggy) code would be represented as:

flowchart TD\n    RDI --> RCX\n    RSI --> RDX\n    RDX --> R8\n    RCX --> R9

RDI writes to RCX which writes to R9, which is now invalid. We can determine the correct mov order, by processing them in reverse order of their dependencies

  • mov r9, rcx before mov rcx, rdi
  • mov r8, rdx before mov rdx, rsi

Exact order encoded depends on algorithm implementation in code; as long as the 2 derived rules are followed.

"},{"location":"dev/design/wrappers/#handling-cycles-2-node-cycle","title":"Handling Cycles (2 Node Cycle)","text":"

Suppose we have 2 calling conventions with reverse parameter order. For this example we will define convention \ud83d\udc31call. \ud83d\udc31call uses the reverse register order of Microsoft compiler.

Cx64 (Microsoft)x64 (\ud83d\udc31call)
int AddWithShift(int a, int b) {\nreturn (a * 16) + b;\n}\n
shl ecx, 4\nlea eax, dword [rdx+rcx]\nret\n
shl edx, 4\nlea eax, dword [rcx+rdx]\nret\n

The ASM to do the calling convention transformation becomes:

UnoptimisedOptimised (Contains Bug)
# Push register parameters of the function being returned (right to left, reverse loop)\npush rcx\npush rdx\n\n# Pop parameters into registers of function being called\npop rcx\npop rdx\n
mov rcx, rdx\nmov rdx, rcx\n

There is now a cycle.

flowchart TD\n    RCX --> RDX\n    RDX --> RCX

In this trivial example, you can use xchg or 3 mov(s) to swap between the two registers.

xchgmov
xchg rcx, rdx\n
mov {temp}, rdx\nmov rdx, rcx\nxor rcx, {temp}\n

On some Intel architectures, the mov approach can reportedly be faster, however, it's not possible to procure a scratch register in all cases.

I'll welcome any PRs that detect and write the more optimal choice on a given architecture, however this is not planned for main library.

Adding instructions also means the wrapper might overflow to the next multiple of 16 bytes, causing more instructions to be fetched when it otherwise won't happend with xchg, potentially losing any benefits gained on those architectures.

The mappings done in Reloaded.Hooks are a 1:1 bijective mapping. Therefore any cycle of just 2 registers can be resolved by simply swapping the involved registers.

"},{"location":"dev/design/wrappers/#handling-cycles-multi-register-cycle","title":"Handling Cycles (Multi Register Cycle)","text":"

Now imagine doing a mapping which involves 3 registers, r8 - r10, and all registers need to be mov'd.

flowchart TD\n    R8 --> R9\n    R9 --> R10\n    R10 --> R8
mov R9, R8\nmov R10, R9\nmov R8, R10\n

To resolve this, we backup the register at the end of the cycle (in this case R10), disconnect it from the first register in the cycle and resolve as normal.

i.e. we solve for

flowchart TD\n    R8 --> R9\n    R9 --> R10

Then write original value of R10 into R8 after this code is converted into mov sequences.

This can be done using the following strategies:

  • mov into scratch register.
    • For mid-function hooks (AsmHook) prefer callee saved register which is not a parameter.
    • For function hooks, use a caller saved register.
  • push + pop register.
ASM (mov scratch)ASM (push+pop)
# Move value from end of cycle into caller saved register (scratch)\nmov RAX, R10\n\n# Original (after reorder)\nmov R10, R9\nmov R9, R8\n\n# Move from caller saved register into first in cycle.\nmov R8, RAX\n
# Push value from end of cycle into stack\npush R10\n\n# Original (after reorder)\nmov R10, R9\nmov R9, R8\n\n# Pop into intended place from stack\npop R8\n

When possible to get scratch register, use mov, otherwise use push.

"},{"location":"dev/design/wrappers/#idea-eliminate-return-address","title":"Idea: Eliminate Return Address","text":"

This is a theoretical idea, not implemented in library.

Only applies to platforms like x86 return addresses on stack.

In some cases, like converting between stdcall and cdecl; it might be possible to reuse the same parameters from the stack. Take into account the previous example:

# Re push parameters\npush dword [esp + {x}]\npush dword [esp + {x}]\n\ncall {function}\nadd esp, 8\n\nret 8\n

Strictly speaking, to convert from stdcall to cdecl, you will only need to convert from caller stack cleanup to callee stack cleanup i.e. ret 8.

In this case, re-pushing parameters is redundant, as the pushed parameters from the previous method call are on stack and can still be re-used.

What we can instead do, is overwrite the return address and jump to our code.

# Pop previous return address from stack\nmov [esp], {addressPostJump} # replace return address\njmp {function} # jump to our function\nadd esp, 8 # our function returns here due to changed return address\nret 8\n
"},{"location":"dev/design/wrappers/#technical-limitations","title":"Technical Limitations","text":"

Wrapper generation does not have understanding of any specific ABI, and as such cannot always be 100% correct in edge cases.

"},{"location":"dev/design/wrappers/#wrappers-dont-understand-abi-specific-rules","title":"Wrappers Don't understand ABI Specific Rules","text":"

Some ABIs have unconventional rules for handling edge cases.

For example, consider the following rule used by the RISC-V ABI.

When primitive arguments twice the size of a pointer-word are passed on the stack, they are naturally aligned. When they are passed in the integer registers, they reside in an aligned even-odd register pair, with the even register holding the least-significant bits. In RV32, for example, the function void foo(int, long long) is passed its first argument in a0 and its second in a2 and a3. Nothing is passed in a1.

The wrappers cannot know or understand the intricate rules such as this that are imposed by an ABI.

"},{"location":"dev/design/wrappers/#allocating-mixed-size-registers-is-tricky","title":"Allocating Mixed Size Registers is Tricky.","text":"

Optimized code does not suffer from this bug.

"},{"location":"dev/design/wrappers/#the-problem","title":"The Problem","text":"

Consider a function which spills a float register xmm0, and an nint (native size integer). A Push is basically a sequence of sub and then mov.

So (pretend ASM below is valid)

push xmm0\npush rax\n

Would become

sub rsp, 16\nmov [rsp], xmm0\nsub rsp, 8\nmov [rsp], rax\n

This is invalid, because the contents of rax will now replace half of the xmm0 register on the stack. How ABIs and compilers deal with this isn't always well standardised; some only consider lower bits volatile, (Microsoft x64) while others don't preserve the bigger registers at all (SystemV x64).

Our strategy will be to try rearrange the stack operations to avoid this problem, starting by pushing smaller registers first, and then larger registers, effectively creating:

sub rsp, 8\nmov [rsp], rax\nsub rsp, 16\nmov [rsp], xmm0\n
"},{"location":"dev/design/wrappers/#when-using-optimized-code","title":"When using Optimized Code","text":"

Currently with optimizations enabled, this code compiles as:

sub rsp, 24\nmov [rsp], xmm0\nmov [rsp + 16], rax\n

Which is valid.

"},{"location":"dev/design/wrappers/#wrappers-currently-dont-understand-how-to-split-larger-registers","title":"Wrappers (Currently) Don't understand how to split larger registers.","text":"

Some calling conventions, have rules where larger values (e.g. 128-bit values on x64) are split into 2 registers.

The wrapper generator cannot generate code for these functions currently.

"},{"location":"dev/design/assembly-hooks/overview/","title":"Assembly Hooks","text":"

Replacing arbitrary assembly sequences (a.k.a. 'mid function hooks').

This hook is used to make small changes to existing logic, for example injecting custom logic for existing conditional branches (if statements).

Limited effectiveness if Code Relocation is not available.

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

This hook works by injecting a jmp instruction inside the middle of an arbitrary assembly sequence to custom code. The person using this hook must be very careful not to break the program (corrupt stack, used registers, etc.).

"},{"location":"dev/design/assembly-hooks/overview/#high-level-diagram","title":"High Level Diagram","text":""},{"location":"dev/design/assembly-hooks/overview/#key","title":"Key","text":"
  • Original Code: Middle of an arbitrary sequence of assembly instructions where a branch to custom code is placed.
  • Hook Function: Contains user code, including original code (depending on user preference).
    • When the hook is deactivated, this contains the original code only.
  • Original Stub: Original code (used when hook disabled).
"},{"location":"dev/design/assembly-hooks/overview/#when-activated","title":"When Activated","text":"
flowchart TD\n    O[Original Code]\n    HK[Hook Function]\n\n    O -- jump --> HK\n    HK -- jump back --> O

When the hook is activated, a branch is placed in the middle of the original assembly instruction sequence to your hook code.

Your code (and/or original code) is then executed, then it branches back to original code.

"},{"location":"dev/design/assembly-hooks/overview/#when-deactivated","title":"When Deactivated","text":"
flowchart TD\n    O[Original Function]\n    HK[\"Hook Function &lt;Overwritten with Original Code&gt;\"]\n\n    O -- jump --> HK\n    HK -- jump back --> O

When the hook is deactivated, the 'Hook Function' is overwritten in-place with original instructions and a jump back to your code.

"},{"location":"dev/design/assembly-hooks/overview/#usage-notes","title":"Usage Notes","text":"

Assembly Hooks should allow both Position Independent Code and Position Relative Code

With that in mind, the following APIs should be possible:

/// Creates an Assembly Hook given existing position independent assembly code,\n/// and address which to hook.\n/// # Arguments\n/// * `hook_address` - The address of the function or mid-function to hook.\n/// * `asm_code` - The assembly code to execute, precompiled.\nfn from_pos_independent_code_and_function_address(hook_address: usize, asm_code: &[u8]);\n\n/// Creates an Assembly Hook given existing position assembly code,\n/// and address which to hook.\n/// \n/// # Arguments\n/// * `hook_address` - The address of the function or mid-function to hook.\n/// * `asm_code` - The assembly code to execute, precompiled.\n/// * `code_address` - The original address of asm_code. \n/// \n/// # Remarks\n/// Code in `asm_code` will be relocated to new target address. \nfn from_code_and_function_address(hook_address: usize, asm_code: &[u8], code_address: usize);\n\n/// Creates an Assembly Hook given existing position assembly code,\n/// and address which to hook.\n/// \n/// # Arguments\n/// * `hook_address` - The address of the function or mid-function to hook.\n/// * `asm_isns` - The assembly instructions to place at this address.\n/// \n/// # Remarks\n/// Code in `asm_code` will be relocated to new target address. \nfn from_instructions_and_function_address(hook_address: usize, asm_isns: &[Instructions]);\n

Using overloads for clarity, in library all options should live in a struct.

Code using from_code_and_function_address is to be preferred for usage, as users will be able to use relative branches for improved efficiency. (If they are out of range, hooking library will rewrite them)

For pure assembly code, users are expected to compile code externally using something like FASM, put the code in their program/mod (as byte array) and pass that directly as asm_code.

For people who want to call their own program/mod(s) from assembly, there will be a wrapper API around Jit<TRegister> and its various Operations. This API will be cross-architecture and should contain all the necessary operations required for setting up stack/registers and calling user code.

Programmers are also expected to provide 'max allowed hook length' with each call.

"},{"location":"dev/design/assembly-hooks/overview/#hook-lengths","title":"Hook Lengths","text":"

The expected hook lengths for each architecture

When using the library, the library will use the most optimal possible jmp instruction to get to the user hook.

When calling one of the functions to create an assembly hook, the end user should specify their max permissible assembly hook length.

If a hook cannot be satisfied within that constraint, then library will throw an error.

The following table below shows common hook lengths, for:

  • Relative Jump (best case)
  • Targeted Memory Allocation (TMA) (expected best case) when above Relative Jump range.
  • Worst case scenario.
Architecture Relative TMA Worst Case x86[1] 5 bytes (+- 2GiB) 5 bytes 5 bytes x86_64 5 bytes (+- 2GiB) 6 bytes[2] 13 bytes[3] x86_64 (macOS) 5 bytes (+- 2GiB) 13 bytes[4] 13 bytes[3] ARM64 4 bytes (+- 128MiB) 12 bytes[6] 20 bytes[5] ARM64 (macOS) 4 bytes (+- 128MiB) 12 bytes[6] 20 bytes[5]

[1]: x86 can reach any address from any address with relative branch due to integer overflow/wraparound. [2]: jmp [<Address>], with <Address> at < 2GiB. [3]: mov <reg>, address + call <reg>. +1 if using an extended reg. [4]: macOS restricts access to < 2GiB memory locations, so absolute jump must be used. +1 if using an extended reg. [5]: MOVZ + MOVK + LDR + BR. [6]: ADRP + ADD + BR.

"},{"location":"dev/design/assembly-hooks/overview/#thread-safety-memory-layout-state-switching","title":"Thread Safety, Memory Layout & State Switching","text":"

Common: Thread Safety & Memory Layout

"},{"location":"dev/design/assembly-hooks/overview/#legacy-compatibility-considerations","title":"Legacy Compatibility Considerations","text":"

As reloaded-hooks-rs intends to replace Reloaded.Hooks is must provide certain functionality for backwards compatibility.

Once reloaded-hooks-rs releases, the legacy Reloaded.Hooks will be a wrapper around it.

This means a few functionalities must be supported here:

  • Setting arbitrary 'Hook Length'.

    • This is the amount of bytes stolen from the original code to be included as 'original code' in hook.
    • On x86 Reloaded.Hooks users create an ASM Hook (with default PreferRelativeJump == false and HookLength == -1) the wrapper for legacy API must set 'Hook Length' == 7 to emulate absolute jump size.
    • Respecting MaxOpcodeSize from original API should be sufficient.
  • Supporting Assembly via FASM.

    • As this is only possible in Windows (FASM can't be recompiled on other OSes as library), this feature will be getting dropped.
    • The Reloaded.Hooks wrapper will continue to ship FASM for backwards compatibility, however mods are expected to migrate to the new library in the future.
"},{"location":"dev/design/assembly-hooks/overview/#limits","title":"Limits","text":"

Assembly hook info is packed by default to save on memory space. By default, the following limits apply:

Property 4 Byte Instruction (e.g. ARM64) Other (e.g. x86) Max Orig Code Length 128KiB 32KiB Max Hook Code Length 128KiB 32KiB

These limits may increase in the future if additional functionality warrants extending metadata length.

"},{"location":"dev/design/branch-hooks/overview/","title":"Branch Hooks","text":"

Replaces a branch (call/jump) to an existing method with a new one.

This hook is commonly used when you want to change behaviour of a function, but only for certain callers.

For example, if you have a method Draw2DElement that's used to draw an object to the screen, but you only want to move a certain element that's rendered by Draw2DElement, you would use a Branch Hook to replace call Draw2DElement to call YourOwn2DElement.

Only guaranteed to work on platforms with Targeted Memory Allocation

Because the library needs to be able to acquire memory in proximity of the original function.

Usually this is almost always achievable, but cases where Denuvo DRM inflates ARM64 binaries (20MB -> 500MB) may prove problematic as ARM64 has +-128MiB range for relative jumps.

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

This hook works by replacing the target of a call (a.k.a. Branch with Link) instruction with a new target.

"},{"location":"dev/design/branch-hooks/overview/#comparison-with-function-hook","title":"Comparison with Function Hook","text":"

A Branch Hook is really a specialised variant of function hook.

Notably it differs in the following ways:

  • There is no Wrapper To Call Original Function as no instructions are stolen.

    • Your method will directly call original instead.
  • You call the ReverseWrapper instead of jumping to it.

  • Code replacement is at caller level rather than function level.
"},{"location":"dev/design/branch-hooks/overview/#high-level-diagram","title":"High Level Diagram","text":""},{"location":"dev/design/branch-hooks/overview/#key","title":"Key","text":"
  • Caller Function: Function which originally called Original Method.
  • ReverseWrapper: Translates from original function calling convention to yours. Then calls your function.
  • <Your Function>: Your Rust/C#/C++/Asm code.
  • Original Method: Original method to be called.
"},{"location":"dev/design/branch-hooks/overview/#when-activated","title":"When Activated","text":"
flowchart TD\n    CF[Caller Function]\n    RW[Stub]\n    HK[\"&lt;Your Function&gt;\"]\n    OM[Original Method]\n\n    CF -- \"call wrapper\" --> RW\n    RW -- jump to your code --> HK\n    HK -. \"Calls &lt;Optionally&gt;\" .-> OM\n    OM -. \"Returns\" .-> HK
"},{"location":"dev/design/branch-hooks/overview/#when-activated-in-fast-mode","title":"When Activated in 'Fast Mode'","text":"

'Fast Mode' is an optimisation that inserts the jmp to point directly into your code when possible.

flowchart TD\n    CF[Caller Function]\n    HK[\"&lt;Your Function&gt;\"]\n    OM[Original Method]\n\n    CF -- \"call 'Your Function' instead of original\" --> HK\n    HK -. \"Calls &lt;Optionally&gt;\" .-> OM\n    OM -. \"Returns\" .-> HK

This option allows for a small performance improvement, saving 1 instruction and some instruction prefetching load.

This is on by default (can be disabled), and will take into effect when no conversion between calling conventions is needed.

"},{"location":"dev/design/branch-hooks/overview/#when-activated-with-calling-convention-conversion","title":"When Activated (with Calling Convention Conversion)","text":"
flowchart TD\n    CF[Caller Function]\n    RW[ReverseWrapper]\n    HK[\"&lt;Your Function&gt;\"]\n    W[Wrapper]\n    OM[Original Method]\n\n    CF -- \"call wrapper\" --> RW\n    RW -- jump to your code --> HK\n    HK -. \"Calls &lt;Optionally&gt;\" .-> W\n    W -- \"call original (wrapped)\" --> OM\n    OM -. \"Returns\" .-> W\n    W -. \"Returns\" .-> HK
"},{"location":"dev/design/branch-hooks/overview/#when-deactivated","title":"When Deactivated","text":"
flowchart TD\n    CF[Caller Function]\n    SB[Stub]\n    HK[Hook Function]\n    OM[Original Method]\n\n    CF -- jump to stub --> SB\n    SB -- jump to original --> OM

When the hook is deactivated, the stub is replaced with a direct jump back to the original function.

By bypassing your code entirely, it is safe for your dynamic library (.dll/.so/.dylib) to unload from the process.

"},{"location":"dev/design/branch-hooks/overview/#thread-safety-memory-layout-state-switching","title":"Thread Safety, Memory Layout & State Switching","text":"

Common: Thread Safety & Memory Layout

"},{"location":"dev/design/branch-hooks/overview/#stub-memory-layout","title":"Stub Memory Layout","text":"

The 'branch hook' stub uses the following memory layout:

- [Branch to Hook Function / Branch to Original Function]\n- Branch to Hook Function\n- Branch to Original Function\n

If calling convention conversion is needed, the layout looks like this:

- [ReverseWrapper / Branch to Original Function]\n- ReverseWrapper\n- Branch to Original Function\n- Wrapper\n

The library is optimised to not use redundant memory

For example, in x86 (32-bit), a jmp instruction can reach any address from any address. In that situation, we don't write Branch to Original Function to the buffer at all, provided a ReverseWrapper is not needed, as it is not necessary.

"},{"location":"dev/design/branch-hooks/overview/#examples","title":"Examples","text":"

Using x86 Assembly.

"},{"location":"dev/design/branch-hooks/overview/#before","title":"Before","text":"
originalCaller:\n; Some code...\ncall originalFunction\n; More code...\n
"},{"location":"dev/design/branch-hooks/overview/#after-fast-mode","title":"After (Fast Mode)","text":"
originalCaller:\n; Some code...\ncall userFunction ; To user method\n; More code...\n\nuserFunction:\n; New function implementation...\ncall originalFunction ; Optional.\n
"},{"location":"dev/design/branch-hooks/overview/#after","title":"After","text":"
; x86 Assembly\noriginalCaller:\n; Some code...\ncall stub\n; More code...\n\nstub:\n; == BranchToHook ==\njmp newFunction\n; == BranchToHook ==\n\n; == BranchToOriginal ==\njmp originalFunction\n; == BranchToOriginal ==\n\nnewFunction:\n; New function implementation...\ncall originalFunction ; Optional.\n
"},{"location":"dev/design/branch-hooks/overview/#after-with-calling-convention-conversion","title":"After (with Calling Convention Conversion)","text":"
; x86 Assembly\noriginalCaller:\n; Some code...\ncall stub\n; More code...\n\nstub:\n; == ReverseWrapper ==\n; implementation..\ncall userFunction\n; ..implementation\n; == ReverseWrapper ==\n\n; == Wrapper ==\n; implementation ..\njmp originalFunction\n; .. implementation\n; == Wrapper ==\n\n; == BranchToOriginal ==\njmp originalFunction ; Whenever disabled :wink:\n; == BranchToOriginal ==\n\nuserFunction:\n; New function implementation...\ncall wrapper; (See Above)\n
"},{"location":"dev/design/branch-hooks/overview/#after-disabled","title":"After (Disabled)","text":"
; x86 Assembly\noriginalCaller:\n; Some code...\ncall stub\n; More code...\n\nstub:\n<jmp to `jmp originalFunction`> ; We disable the hook by branching to instruction that branches to original\njmp originalFunction ; Whenever disabled :wink:\n\nnewFunction:\n; New function implementation...\ncall originalFunction ; Optional.\n\noriginalFunction:\n; Original function implementation...\n
"},{"location":"dev/design/function-hooks/hooking-strategy-arm64/","title":"Interoperability (ARM64)","text":"

Please read the general section first, this contains ARM64 specific stuff.

"},{"location":"dev/design/function-hooks/hooking-strategy-arm64/#fallback-strategy-free-space-from-function-alignment","title":"Fallback Strategy: Free Space from Function Alignment","text":"

See General Section Notes.

In the case of ARM64, padding is usually down with the following sequences: - nop (0xD503201F, big endian), used by GCC. - and x0, x0 (0x00000000), used by MSVC.

Getting sufficient bytes to make good use of them in ARM64 is more uncommon than x86.

"},{"location":"dev/design/function-hooks/hooking-strategy-x86/","title":"Interoperability (x86)","text":"

Please read the general section first, this contains x86 specific stuff.

"},{"location":"dev/design/function-hooks/hooking-strategy-x86/#fallback-strategy-free-space-from-function-alignment","title":"Fallback Strategy: Free Space from Function Alignment","text":"

See General Section Notes.

  • x86 programs align instructions on 16 byte boundaries.
  • Bytes 0x90 (GCC) or 0xCC (MSVC) are commonly used for padding.
"},{"location":"dev/design/function-hooks/hooking-strategy-x86/#fallback-strategy-return-address-patching","title":"Fallback Strategy: Return Address Patching","text":"

See General Section Notes.

We use x86 in the example for general section above.

"},{"location":"dev/design/function-hooks/hooking-strategy/","title":"Interoperability (General)","text":"

This page just contains common information regarding interoperability that are common to all platforms.

Interpoerability in this sense means 'stacking hooks ontop of other libraries', and how other libraries can stack hooks ontop of reloaded-hooks-rs.

"},{"location":"dev/design/function-hooks/hooking-strategy/#general-hooking-strategy","title":"General Hooking Strategy","text":"

This is the general hooking strategy employed by reloaded-hooks; derived from the facts in the rest of this document.

To ensure maximum compatibility with existing hooking systems, reloaded-hooks uses relative jumps as these are the most popular, and thus best supported by other libraries when it comes to hook stacking.

These are the lowest overhead jumps, so are preferable in any case.

"},{"location":"dev/design/function-hooks/hooking-strategy/#if-relative-jump-is-not-possible","title":"If Relative Jump is Not Possible","text":"

In the very, very, unlikely event that using (target is further than max relative jump distance), the following strategy below is used.

"},{"location":"dev/design/function-hooks/hooking-strategy/#no-existing-hook","title":"No Existing Hook","text":"

If no existing hook exists, an absolute jump will be used (if possible). - Prefer indirect absolute jump (if possible).

We check for presence of 'existing hook' by catching some common instruction patterns.

"},{"location":"dev/design/function-hooks/hooking-strategy/#existing-hook","title":"Existing Hook","text":"
  • If we have any allocated buffer in range, insert relative jump, and inside wrapper/stub use absolute jump if needed.

    • This prevents your hook longer than original error case.
  • Otherwise (if possible), use available free space from function alignment.

    • If supported IP Relative Jmp, with target address in free space.
    • Otherwise try store whole absolute jump, in said alignment space.
  • Otherwise use absolute jump.

    • And attempt return address patching, if this is ever re-implemented into library.
"},{"location":"dev/design/function-hooks/hooking-strategy/#calling-back-into-original-function","title":"Calling Back into Original Function","text":"

In order to optimize the code relocation process, reloaded-hooks, will try to find a buffer that's within relative jump range to the original jump target.

If this is not possible, reloaded-hooks will start rewriting relative jump(s) from the original function to absolute jump(s) in the presence of recognised patterns; if the code rewriter supports this.

"},{"location":"dev/design/function-hooks/hooking-strategy/#fallback-strategies","title":"Fallback Strategies","text":"

Strategies used for improving interoperability with other hooks.

"},{"location":"dev/design/function-hooks/hooking-strategy/#free-space-from-function-alignment","title":"Free Space from Function Alignment","text":"

This is a strategy for encoding absolute jumps using fewer instructions.

Processors typically fetch instructions 16 byte boundaries.

To optimise for this, compilers pad the space between end of last function and start of next.

We can exploit this \ud83d\ude09

If there's sufficient padding before the function, we can: - Insert our absolute jump there, and branch to it. or - Insert jump target there, and branch using that jump target.

"},{"location":"dev/design/function-hooks/overview/","title":"Function Hooks","text":"

How hooking around entire functions works.

This hook is used to run custom callback for a function, modify its parameters or replace a function entirely. It is the most common hook.

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

This hook works by injecting a jmp instruction at the beginning of a function to a custom replacement function, or a stub which will later call that function.

When the original function is called, it is done via a wrapper, which restores the originally overwritten instructions that were sacrificed for the jmp.

"},{"location":"dev/design/function-hooks/overview/#high-level-diagram","title":"High Level Diagram","text":""},{"location":"dev/design/function-hooks/overview/#key","title":"Key","text":"
  • Stolen Bytes: Bytes used by instructions sacrificed in original function to place a 'jmp' to the ReverseWrapper.
  • ReverseWrapper: Translates from original function calling convention to yours. Then calls your function.
  • <Your Function>: Your Rust/C#/C++/Asm code.
  • Wrapper: Translates from your calling convention to original, then runs the original function.
"},{"location":"dev/design/function-hooks/overview/#when-activated","title":"When Activated","text":"
flowchart TD\n    orig[Original Function] -- jump to wrapper --> rev[Reverse Wrapper]\n    rev -- jump to your code --> target[\"&lt;Your Function&gt;\"]\n    target -- \"call original via wrapper\" --> stub[\"Wrapper &lt;with stolen bytes + jmp to original&gt;\"]\n    stub -- \"call original\" --> original[\"Original Function\"]\n\n    original -- \"return value\" --> stub\n    stub -- \"return value\" --> target

When the hook is activated, a stub calls into your function; which becomes the 'new original function'; that is, control will return (ret) to the original function's caller from this function.

When your function calls the original function, it will be an entirely separate method call.

Your function can technically not call the original and replace it outright.

"},{"location":"dev/design/function-hooks/overview/#when-activated-in-fast-mode","title":"When Activated in 'Fast Mode'","text":"

'Fast Mode' is an optimisation that inserts the jmp to point directly into your code when possible.

flowchart TD\n    orig[Original Function] -- to your code --> target[\"&lt;Your Function&gt;\"]\n    target -- \"call original via wrapper\" --> stub[\"Wrapper &lt;with stolen bytes + jmp to original&gt;\"]\n    stub -- \"call original\" --> original[\"Original Function\"]\n\n    original -- \"return value\" --> stub\n    stub -- \"return value\" --> target

This option allows for a small performance improvement, saving 1 instruction and some instruction prefetching load.

This is on by default (can be disabled), and will take into effect when no conversion between calling conventions is needed.

When conversion is needed, the logic will default back to When Activated.

When 'Fast Mode' is enabled, you lose the ability to unhook (for compatibility reasons).

"},{"location":"dev/design/function-hooks/overview/#when-deactivated","title":"When Deactivated","text":"

Does not apply to 'Fast Mode'. When in fast mode, deactivation returns error.

flowchart TD\n    orig[Original Function] -- jump to wrapper --> stub[\"Stub &lt;stolen bytes + jmp&gt;\"]\n    stub -- \"jmp original\" --> original[\"Original Function\"]

When you deactivate a hook, the contents of 'Reverse Wrapper' are overwritten with the stolen bytes.

When 'Reverse Wrapper' is allocated, extra space is reserved for original code.

By bypassing your code entirely, it is safe for your dynamic library (.dll/.so/.dylib) to unload from the process.

"},{"location":"dev/design/function-hooks/overview/#calling-convention-inference","title":"Calling Convention Inference","text":"

It is recommended library users manually specify conventions in their hook functions.\"

When the calling convention of <your function> is not specified, wrapper libraries must insert the appropriate default convention in their wrappers.

On Linux, syscalls use R10 instead of RCX in SystemV ABI

"},{"location":"dev/design/function-hooks/overview/#rust","title":"Rust","text":"
  • i686-pc-windows-gnu: cdecl
  • i686-pc-windows-msvc: cdecl
  • i686-unknown-linux-gnu: SystemV (x86)

  • x86_64-pc-windows-gnu: Microsoft x64

  • x86_64-pc-windows-msvc: Microsoft x64
  • x86_64-unknown-linux-gnu: SystemV (x64)
  • x86_64-apple-darwin: SystemV (x64)
"},{"location":"dev/design/function-hooks/overview/#c","title":"C","text":"
  • Windows x86: cdecl
  • Windows x64: Microsoft x64

  • Linux x64: SystemV (x64)

  • Linux x86: SystemV (x86)

  • macOS x64: SystemV (x64)

"},{"location":"dev/design/function-hooks/overview/#wrappers","title":"Wrapper(s)","text":"

Wrappers are stubs which convert from the calling convention of the original function to your calling convention.

If the calling convention of the hooked function and your function matches, this wrapper is simply just 1 jmp instruction.

Wrappers are documented in their own page here.

"},{"location":"dev/design/function-hooks/overview/#reversewrappers","title":"ReverseWrapper(s)","text":"

Stub which converts from your code's calling convention to original function's calling convention

This is basically Wrapper with source and destination swapped around

"},{"location":"dev/design/vtable-hooks/overview/","title":"VTable Hooks","text":"

Replaces a pointer inside an array of function pointers with a new pointer.

This hook is commonly used to hook COM objects, e.g. Direct3D.

I'm not a security person/researcher. I just make full stack game modding tools, mods and libraries. Naming in these design docs might be unconventional.

Probably the simplest hook out of them all, it's simply replacing one pointer inside an array of function pointers with a new one.

"},{"location":"dev/design/vtable-hooks/overview/#about-vtables","title":"About VTables","text":"

VTables, are what is used to support polymorphism in C++ and similar languages.

They are the mechanism that enables calling correct functions in presence of inheritance and virtual functions.

Basically what drives 'interfaces' in other languages.

"},{"location":"dev/design/vtable-hooks/overview/#vtables-in-msvc-gcc","title":"VTables in MSVC & GCC","text":"

In both GCC and Visual C++, VTables are automatically created for classes that have virtual functions.

They are located at offset 0x0 of any class, thus if you get a pointer to a class, and dereference offset 0x0, you'll be at the address of the first item in the VTable.

C++Memory Layout
class Item {\nvirtual void doSomething();\nint k;\n};\n
class Item\n    void* vTable\n    int k\n
vTable:\n    void* doSomething\n

VTables exist in .rdata, thus you need to change memory permissions when hooking them.

"},{"location":"dev/design/vtable-hooks/overview/#vtables-in-com-objects","title":"VTables in COM Objects","text":"

One notable thing about COM is that all interfaces inherit from IUnknown, so the first 4 methods will always be the 4 methods of IUnknown.

"},{"location":"dev/design/vtable-hooks/overview/#high-level-diagram","title":"High Level Diagram","text":"

Using Direct3D9 as an example

"},{"location":"dev/design/vtable-hooks/overview/#before","title":"Before","text":"
flowchart LR\n    EndScene --> EndScene_Orig \n    Clear --> Clear_Orig\n    SetTransform --> SetTransform_Orig\n    GetTransform  --> GetTransform_Orig
"},{"location":"dev/design/vtable-hooks/overview/#after","title":"After","text":"
flowchart LR\n    EndScene --> EndScene_Hook --> Your_Function --> EndScene_Orig\n    Clear --> Clear_Orig\n    SetTransform --> SetTransform_Orig\n    GetTransform  --> GetTransform_Orig
"},{"location":"dev/platform/overview/","title":"Platform Overview","text":"

This page provides a list of platform specific functionality required for supporting Reloaded.Hooks-rs.

  • Required means library must have this to function.
  • Recommended means library may not work on some edge cases.
  • Optional means library can function without it.

To add support for new platforms, supply the necessary function pointers in platform_functions.rs.

Feature Windows Linux macOS Permission Change \u2705 \u2705 \u2705 W^X Disable/Restore N/A N/A [1] \u26a0\ufe0f [2] Targeted Memory Allocation \u2705 \u2705 \u2705

[1] May be present depending on kernel configuration. Have not done adequate research. [2] Needed for Apple Silicon only.

"},{"location":"dev/platform/overview/#how-to-implement-support","title":"How to Implement Support","text":"

Once you're done, submit a PR to add support for your platform.

"},{"location":"dev/platform/overview/#platform-functions","title":"Platform Functions","text":"

The library provides a platform_functions.rs file which contains all the platform specific functions.

Implement the functions in this file for your platform. Generally you'll only need unprotect_memory, though on some platforms, you may need to implement disable_write_xor_execute and restore_write_xor_execute as well, depending on the platform's security policy.

"},{"location":"dev/platform/overview/#recommended-buffers-implementation","title":"(Recommended) Buffers Implementation","text":"

For optimal performance, you should add support for your platform to reloaded-memory-buffers.

It's recommended to use reloaded-hooks-rs alongside reloaded-memory-buffers. The concept of the buffers library is to perform allocations as close to original code as possible, allowing for more efficient code.

This requires walking memory pages. If your OS does not have a way to do this, you can in the meantime use the built-in DefaultBufferFactory. On some platforms you'll also need to adjust DefaultBufferFactory::create_page_as_rx, if your platform does not allow RWX allocations.

For DefaultBufferFactory, you might need to replace mmap_rs in get_any_buffer to use your platform specific page allocation function.

"},{"location":"dev/platform/overview/#testing-your-implementation","title":"Testing Your Implementation","text":"

Platform specific functionality is not unit tested as it relies on OS/system state. Instead, integration tests are used to test the functionality.

Find the tests for a given hook type (recommend: assembly_hook tests) and run them on your platform.

If you can't run tests on your platform, copy them to one of your programs manually.

"},{"location":"dev/platform/overview/#required-permission-change","title":"(Required) Permission Change","text":"

Many platforms have per-page access permissions; which may prevent certain regions of memory from being modified.

Notably for the use cases of this library, the .text section is usually non-writeable, which prevents hooking app functions out of the box.

To work around this, the library will call the unprotect function in platform_functions.rs before making code changes in memory. It will then (for performance reasons) leave the memory unprotected for the lifetime of the process (assuming it remains unprotected).

For the common operating systems; the protect/unprotect functions map to the following API calls:

  • Windows: VirtualProtect
  • Linux & macOS: mprotect
"},{"location":"dev/platform/overview/#required-wx-disablerestore","title":"(Required) W^X Disable/Restore","text":"

Only required on Apple, opt in on Linux/Windows but haven't used in a game software in the wild.

Info

Some platforms enforce a security protection called 'Write XOR Execute'; where a memory page may only be marked as writeable OR executable at any moment in time.

  • Relevant Issue for macOS M1

To work around this, the library will call the disable_write_xor_execute function in platform_functions.rs ahead of every function call. It will then call restore_write_xor_execute after.

"},{"location":"dev/platform/overview/#recommended-targeted-memory-allocation","title":"(Recommended) Targeted Memory Allocation","text":"

Info

The process of code relocation might require that new location of the code is within a certain region of the old code, usually 128MiB, 2GiB or 4GiB (depending on platform).

In this case, you must walk over the memory pages of a process; and find a suitable place to allocate \ud83d\ude09

"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..46e50a6 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,148 @@ + + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/contributing/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/Readme/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/Pages/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/Pages/contributing/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/Pages/license/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/Pages/testing-zone/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/docs/Pages/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/docs/Pages/contributing/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/docs/Pages/license/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/Reloaded/docs/Pages/testing-zone/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/operations-impl/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/operations/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/overview/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/arm64/aarch64/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/arm64/code_relocation/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/x86/code_relocation/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/x86/x86/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/arch/x86/x86_64/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/common/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/wrappers/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/assembly-hooks/overview/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/branch-hooks/overview/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/function-hooks/hooking-strategy-arm64/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/function-hooks/hooking-strategy-x86/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/function-hooks/hooking-strategy/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/function-hooks/overview/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/design/vtable-hooks/overview/ + 2023-12-30 + daily + + + https://github.com/Reloaded-Project/Reloaded.Hooks-rs/dev/platform/overview/ + 2023-12-30 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000..bf77b51 Binary files /dev/null and b/sitemap.xml.gz differ