├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── zone │ │ └── rong │ │ └── mixinbooter │ │ ├── Context.java │ │ ├── IEarlyMixinLoader.java │ │ ├── ILateMixinLoader.java │ │ ├── IMixinConfigHijacker.java │ │ ├── MixinBooterMixinPlugin.java │ │ ├── MixinBooterModContainer.java │ │ ├── MixinBooterPlugin.java │ │ ├── MixinLoader.java │ │ ├── fix │ │ ├── MixinFixer.java │ │ ├── forge │ │ │ └── EagerlyLoadEventClassTransformer.java │ │ ├── mixinextras │ │ │ └── MixinExtrasFixer.java │ │ └── spongeforge │ │ │ └── SpongeForgeFixer.java │ │ └── mixin │ │ ├── CrashReportMixin.java │ │ └── LoadControllerMixin.java └── resources │ ├── icon.png │ ├── mcmod.info │ └── mixin.mixinbooter.init.json └── relocate └── java └── org └── objectweb └── asm └── commons └── ClassRemapper.java /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+' 7 | - '[0-9]+.[0-9]+.[0-9]+' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Grant Execute Permission for gradlew 17 | run: chmod +x gradlew 18 | 19 | - name: Setup Java 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '17' 23 | distribution: 'zulu' 24 | cache: gradle 25 | 26 | - name: Publish to Maven 27 | uses: gradle/gradle-build-action@v2 28 | with: 29 | arguments: | 30 | publish 31 | -PCleanroomMaven=${{ vars.CLEANROOM_MAVEN }} 32 | -PCleanroomMavenUsername=${{ secrets.MAVEN_NAME }} 33 | -PCleanroomMavenPassword=${{ secrets.MAVEN_PASSWORD }} 34 | 35 | - name: Upload Artifacts 36 | uses: actions/upload-artifact@v4 37 | with: 38 | path: build/libs/ 39 | upload: 40 | needs: build 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout Repository 44 | uses: actions/checkout@v3 45 | 46 | - name: Download Artifacts 47 | uses: actions/download-artifact@v4 48 | 49 | - name: Read gradle.properties 50 | uses: BrycensRanch/read-properties-action@v1 51 | id: properties 52 | with: 53 | file: gradle.properties 54 | all: true 55 | 56 | - name: Read CHANGELOG.md 57 | id: changelog 58 | uses: 3liz/changelog-release@0.2.0 59 | with: 60 | add_emojis: false 61 | 62 | - name: Publish to CurseForge + Modrinth 63 | uses: Kir-Antipov/mc-publish@v3.3 64 | with: 65 | name: ${{ steps.properties.outputs.mod_name }} ${{ steps.properties.outputs.mod_version }} 66 | version: ${{ steps.properties.outputs.mod_version }} 67 | version-type: ${{ steps.properties.outputs.version_type }} 68 | changelog: ${{ steps.changelog.outputs.markdown }} 69 | loaders: forge 70 | game-versions: | 71 | [1.8,1.12.2] 72 | game-version-filter: releases 73 | java: 8 74 | files: | 75 | artifact/!(*-@(dev|sources|javadoc)).jar 76 | artifact/*-@(dev).jar 77 | artifact/*-@(sources).jar 78 | artifact/*-@(javadoc).jar 79 | 80 | github-token: ${{ secrets.GITHUB_TOKEN }} 81 | 82 | curseforge-id: 419286 83 | curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} 84 | 85 | modrinth-id: G1ckZuWK 86 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | 24 | # Files from Forge MDK 25 | forge*changelog.txt 26 | 27 | logs/ 28 | /localGitDependency/ 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [10.6] - 2025-03-25 4 | 5 | ### Changed 6 | - Updated MixinExtras from 0.5.0-beta.5 to 0.5.0-rc.1 7 | 8 | ### Fixed 9 | - GsonBuilder#setLenient not existing on older Gson versions 10 | 11 | ## [10.5] - 2025-02-03 12 | 13 | ### Added 14 | - Explicit compatibility with checking if Optifine is loaded at coremod stages (for the Context obj) 15 | - Hotswap Agent related manifest entries to allow hotswapping outside of dev 16 | 17 | ### Fixed 18 | - Edge-cases in reading some mods' mcmod.infos 19 | 20 | ## [10.4] - 2025-01-24 21 | 22 | ### Changed 23 | - Made reading mod metadata quicker and simpler 24 | - Updated MixinExtras to 0.5.0-beta.5 25 | 26 | ### Fixed 27 | - SpongeForge compatibility, due to a Forge oversight 28 | 29 | ## [10.3] - 2025-01-14 30 | 31 | ### Added 32 | - Context to `IMixinConfigHijacker` 33 | 34 | ### Changed 35 | - Allowed all mixin configs to be hijacked 36 | 37 | ### Fixed 38 | - Use `JsonReader#setLenient` over `GsonBuilder#setLenient` for older Gson versions supplied by older Minecraft versions 39 | - Use deprecated `Handle` constructor for older ASM versions supplied by older Minecraft versions 40 | - Catch loader issues instead of exiting and not loading subsequent loaders 41 | 42 | ## [10.2] - 2024-11-16 43 | 44 | ### Fixed 45 | - Specified ordinality on a local capture for when the mixin is applied in brittle contexts 46 | 47 | ## [10.1] - 2024-11-04 48 | 49 | ### Fixed 50 | - Few certain vanilla classes loading early, breaking some deobfuscation into SRG 51 | 52 | ## [10.0] - 2024-11-02 53 | 54 | ### Added 55 | - New API (Context) for early/late loaders 56 | 57 | ### Changed 58 | - Updated UniMix, is now updated with Fabric Mixin 0.15.3, Mixin 0.8.7 59 | - Updated to MixinExtras 0.5.0-beta4 60 | - Better logging at launch 61 | 62 | ### Fixed 63 | - Hijackers not applied to late mixins 64 | - Mod description typo 65 | 66 | ## [9.4] - 2024-09-20 67 | 68 | ### Changed 69 | - Updated UniMix 70 | - Enable `comformVisibility` to `true` by default - thanks to jbredwards 71 | 72 | ### Fixed 73 | - Compatibility with older ASM libraries - thanks to HowardZHY 74 | 75 | ## [9.3] - 2024-08-18 76 | 77 | ### Changed 78 | - Updated MixinExtras to 0.3.6 79 | 80 | ## [9.2] - 2024-08-08 81 | 82 | ### Changed 83 | - Reinstated `@MixinLoader` annotation, primarily for 1.8.x usages as Forge does not support gathering of interfaces 84 | 85 | ### Fixed 86 | - Mixin source files not embed within the source jar 87 | - 1.8.x related crashes fixed (thanks to @HowardZHY!) 88 | 89 | ## [9.1] - 2024-02-03 90 | 91 | ### Changed 92 | - Removed fastutil usages, to keep compatibility with Minecraft versions that used different fastutil versions 93 | 94 | ## [9.0] - 2024-01-30 95 | 96 | ### Added 97 | - `IMixinConfigHijacker` API, allows denial of mixin configurations from being applied (idea: @Desoroxxx) 98 | 99 | ### Changed 100 | - Updated MixinExtras to 0.3.5 101 | 102 | ### Fixed 103 | - Mixin information being duplicated in crash reports in certain situations 104 | 105 | ## [8.9] - 2023-11-02 106 | 107 | ### Changed 108 | - Updated MixinExtras from 0.2.0-beta.9 to 0.2.1-beta.2, now supports wrapping of object instantiations with `@WrapOperation` 109 | 110 | ## [8.8] - 2023-10-31 111 | 112 | ### Changed 113 | - Logging during mod gathering 114 | 115 | ### Fixed 116 | - Optimized mod gathering 117 | - Fixed delegated transformers not being rebuild before late mixins are loaded. Resulting in transformers that are registered later not properly running 118 | 119 | ## [8.7] - 2023-10-30 120 | 121 | ### Fixed 122 | - Fixed issues with different builds of DJ2Addons. 123 | 124 | ## [8.6] - 2023-09-14 125 | 126 | ### Added 127 | - Added a dummy mcmod.info 128 | 129 | ### Changed 130 | - Only allowing SpongeForge 7.4.8+ from loading with MixinBooter. 131 | 132 | ### Fixed 133 | - Fixed issue with Modrinth uploads (not allowing forge mods without mcmod.info packaged to be uploaded) 134 | 135 | ## [8.5] - 2023-09-13 136 | 137 | ### Added 138 | - Implemented FMLContextQuery, and MixinContextQuery which is extensible for different platforms 139 | 140 | ### Changed 141 | - Better description for the mod 142 | 143 | ### Fixed 144 | - Made SpongeForge's PrettyPrinter backwards-compatible, more fixes may follow up if bugs are found 145 | - Compatibility with Uncrafting Blacklist 146 | 147 | ## [8.4] - 2023-08-12 148 | 149 | ### Changed 150 | - Allows the entire exception chain to be inspected during mixin metadata search 151 | - Eliminated lots of Reflection, uses Unsafe in some areas 152 | 153 | ### Fixed 154 | - Fixed compatibility with Forge's interface annotation not being respected 155 | - Fixed crash with SpongeForge 156 | - Fixed duplicated mixin metadata being printed 157 | - Fixed majority of mod incompatibility, properly -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MixinBooter 2 | ### Allows any mixins that work on mods to work effortlessly on 1.8 - 1.12.2 3 | 4 | - Current Mixin Version: [UniMix 0.15.3 forked by CleanroomMC, derived from 0.8.7 branch by LegacyModdingMC](https://github.com/CleanroomMC/UniMix) 5 | 6 | - Current MixinExtra Version: [0.5.0-rc.1](https://github.com/LlamaLad7/MixinExtras) 7 | 8 | ### Pseudo-Changelog: 9 | 10 | - As of 4.2, MixinBooter's API has changed and ***all mods*** that uses mixins are encouraged to depend on MixinBooter, even those that mixin into vanilla/forge/library classes. To avoid mixin version mismatches with mods crashing trying to implement modded mixins (looking at you VanillaFix). Thanks to [@embeddedt](https://github.com/embeddedt) recommending and helping me introduce this change! 11 | 12 | - As of 5.0, [MixinExtras by @LlamaLad7](https://github.com/LlamaLad7/MixinExtras) is shaded. Available for developers to use. 13 | 14 | - As of 8.0, MixinBooter will now work from 1.8 - 1.12.2. One single build works with all these versions! (TODO: LiteLoader support?) 15 | 16 | - As of 8.4, MixinBooter actively attempts to be compatible with [SpongeForge](https://github.com/SpongePowered/SpongeForge) 17 | 18 | - As of 9.2, MixinBooter reinstates the older MixinLoader annotation for 1.8.x usages. 19 | 20 | - As of 10.0, MixinBooter follows Mixin 0.8.7 21 | 22 | ### For Developers ~ Getting Started: 23 | 24 | 1. Add CleanroomMC's repository and depend on MixinBooter's maven entry: 25 | 26 | ```groovy 27 | repositories { 28 | maven { 29 | url 'https://maven.cleanroommc.com' 30 | } 31 | } 32 | 33 | dependencies { 34 | 35 | // Common: 36 | annotationProcessor 'org.ow2.asm:asm-debug-all:5.2' 37 | annotationProcessor 'com.google.guava:guava:32.1.2-jre' 38 | annotationProcessor 'com.google.code.gson:gson:2.8.9' 39 | 40 | // ForgeGradle: 41 | implementation ('zone.rong:mixinbooter:10.6') { 42 | transitive = false 43 | } 44 | annotationProcessor ('zone.rong:mixinbooter:10.6') { 45 | transitive = false 46 | } 47 | 48 | // RetroFuturaGradle: 49 | String mixinBooter = modUtils.enableMixins('zone.rong:mixinbooter:10.6') 50 | // modUtils.enableMixins('zone.rong:mixinbooter:10.6', 'mod_id.mixins.refmap.json') << add refmap name as 2nd arg (optional) 51 | api (mixinBooter) { 52 | transitive = false 53 | } 54 | annotationProcessor (mixinBooter) { 55 | transitive = false 56 | } 57 | } 58 | 59 | ``` 60 | 61 | 2. Pick your path: 62 | 63 | - Mixin into minecraft, forge or coremods? Make a *coremod* with `IFMLLoadingPlugin` and implement the class with `IEarlyMixinLoader` 64 | - Mixin into normal mods? Make a normal class anywhere in your mod file, implement the class with `ILateMixinLoader` 65 | 66 | 3. Register your mixin configs 67 | - In either your `IEarlyMixinLoader` or `ILateMixinLoader` you have to return a list of mixin config names via the `getMixinConfigs` method 68 | - This is the path (relative to your `resources` root) to your mixin config. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.gradle.ext.Gradle 2 | 3 | plugins { 4 | id 'java' 5 | id 'java-library' 6 | id 'maven-publish' 7 | id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1.7' 8 | id 'com.gtnewhorizons.retrofuturagradle' version '1.3.16' 9 | } 10 | 11 | def coremod_plugin_name = 'zone.rong.mixinbooter.MixinBooterPlugin' 12 | 13 | version = mod_version 14 | group = 'zone.rong' 15 | archivesBaseName = 'mixinbooter' 16 | 17 | java { 18 | toolchain { 19 | languageVersion.set(JavaLanguageVersion.of(8)) 20 | vendor.set(org.gradle.jvm.toolchain.JvmVendorSpec.AZUL) 21 | } 22 | // Generate sources and javadocs jars when building and publishing 23 | withSourcesJar() 24 | withJavadocJar() 25 | } 26 | 27 | tasks.withType(JavaCompile).configureEach { 28 | options.encoding = "UTF-8" 29 | } 30 | 31 | minecraft { 32 | mcVersion = '1.12.2' 33 | extraRunJvmArguments.addAll([ 34 | "-ea:${project.group}", 35 | "-Dfml.coreMods.load=${coremod_plugin_name}", 36 | '-Dmixin.checks.interfaces=true', 37 | '-Dmixin.debug.export=true' 38 | ]) 39 | extraTweakClasses.add('org.spongepowered.asm.launch.MixinTweaker') 40 | injectedTags.put('VERSION', project.version) 41 | injectedTags.put('MOD_ID', project.archivesBaseName) 42 | injectedTags.put('MOD_NAME', project.mod_name) 43 | } 44 | 45 | tasks.injectTags.configure { 46 | outputClassName.set("zone.rong.mixinbooter.Tags") 47 | } 48 | 49 | sourceSets { 50 | relocate { 51 | compileClasspath += sourceSets.main.compileClasspath 52 | runtimeClasspath += sourceSets.main.runtimeClasspath 53 | compileClasspath += sourceSets.main.output 54 | runtimeClasspath += sourceSets.main.output 55 | } 56 | } 57 | 58 | configurations { 59 | embed 60 | mixin 61 | implementation.extendsFrom embed 62 | embed.extendsFrom mixin 63 | annotationProcessor.extendsFrom mixin 64 | } 65 | 66 | repositories { 67 | maven { 68 | url 'https://jitpack.io' 69 | } 70 | // maven { 71 | // name 'Fabric' 72 | // url 'https://maven.fabricmc.net/' 73 | // } 74 | } 75 | 76 | dependencies { 77 | String mixinDep = modUtils.enableMixins('com.github.CleanroomMC:UniMix:9d4b487ed3') 78 | mixin (mixinDep) { 79 | transitive = false 80 | } 81 | embed 'io.github.llamalad7:mixinextras-common:0.5.0-rc.1' 82 | annotationProcessor 'org.ow2.asm:asm-debug-all:5.2' 83 | annotationProcessor 'com.google.guava:guava:24.1.1-jre' 84 | annotationProcessor 'com.google.code.gson:gson:2.8.6' 85 | } 86 | 87 | processResources { 88 | inputs.property 'mod_version', project.version 89 | inputs.property 'mod_id', project.archivesBaseName 90 | inputs.property 'mod_name', project.mod_name 91 | 92 | filesMatching('mcmod.info') { 93 | expand( 94 | 'mod_version': project.version, 95 | 'mod_id': project.archivesBaseName, 96 | 'mod_name': project.mod_name, 97 | ) 98 | } 99 | } 100 | 101 | tasks.register('properJar', Jar) { jar -> 102 | 103 | group 'build' 104 | 105 | // Gather all service files, append them under the same file if possible 106 | doFirst { 107 | def serviceDir = file("$buildDir/tmp/services_cleanup/META-INF/services") 108 | serviceDir.deleteDir() 109 | serviceDir.mkdirs() 110 | 111 | configurations.embed.each { file -> 112 | zipTree(file).matching { 113 | include 'META-INF/services/*' 114 | }.each { serviceFile -> 115 | new File(serviceDir, serviceFile.name) << serviceFile.getText("UTF-8") << '\n' 116 | } 117 | } 118 | } 119 | 120 | // Set output jar name to have an exclamation mark at the front 121 | jar.archiveBaseName.set("!${project.archivesBaseName}") 122 | 123 | // Gather all main source/resource files, excluding any META-INF and replaceable files 124 | from sourceSets.main.output 125 | 126 | // Gather relocate source set's source files 127 | from sourceSets.relocate.output 128 | 129 | // Gather embed dependencies, while excluding any replaceable files and META-INF services 130 | from(provider { 131 | configurations.embed.collect { 132 | if (it.isDirectory()) { 133 | return it 134 | } else { 135 | zipTree(it).matching { 136 | exclude '**/LICENSE*', 'META-INF/**' 137 | } 138 | } 139 | } 140 | }) 141 | 142 | // Gather earlier bundled service files 143 | from fileTree(file("$buildDir/tmp/services_cleanup/")).matching { 144 | include 'META-INF/services/*' 145 | } 146 | 147 | manifest { 148 | def attribute_map = [:] 149 | attribute_map['TweakClass'] = 'org.spongepowered.asm.launch.MixinTweaker' 150 | attribute_map['FMLCorePlugin'] = coremod_plugin_name 151 | attribute_map['FMLCorePluginContainsFMLMod'] = true 152 | attribute_map['ForceLoadAsMod'] = project.gradle.startParameter.taskNames[0] == "build" 153 | attribute_map['Premain-Class'] = 'org.spongepowered.tools.agent.MixinAgent' 154 | attribute_map['Agent-Class'] = 'org.spongepowered.tools.agent.MixinAgent' 155 | attribute_map['Can-Redefine-Classes'] = true 156 | attribute_map['Can-Retransform-Classes'] = true 157 | 158 | attributes(attribute_map) 159 | } 160 | } 161 | 162 | tasks.register('properSourcesJar', Jar) { jar -> 163 | 164 | group 'build' 165 | 166 | // Sources classifier 167 | jar.archiveClassifier.set('sources') 168 | 169 | // Gather all of our own source files 170 | from sourceSets.main.allSource 171 | 172 | // Gather all of relocated source files 173 | from sourceSets.relocate.allSource 174 | 175 | // Gather sources of embed dependencies 176 | def componentIds = configurations.embed.incoming.resolutionResult.allDependencies.collect { it.selected.id } 177 | ArtifactResolutionResult result = dependencies.createArtifactResolutionQuery() 178 | .forComponents(componentIds) 179 | .withArtifacts(JvmLibrary, SourcesArtifact) 180 | .execute() 181 | result.resolvedComponents.each { ComponentArtifactsResult component -> 182 | Set sources = component.getArtifacts(SourcesArtifact) 183 | sources.each { ArtifactResult ar -> 184 | if (ar instanceof ResolvedArtifactResult) { 185 | fileTree(ar.file).each { sourceFiles -> 186 | from zipTree(sourceFiles).matching { 187 | include '**/*.java' 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | 195 | idea { 196 | module { 197 | inheritOutputDirs = true 198 | } 199 | project { 200 | settings { 201 | runConfigurations { 202 | "1. Run Client"(Gradle) { 203 | taskNames = ["runClient"] 204 | } 205 | "2. Run Server"(Gradle) { 206 | taskNames = ["runServer"] 207 | } 208 | "3. Run Obfuscated Client"(Gradle) { 209 | taskNames = ["runObfClient"] 210 | } 211 | "4. Run Obfuscated Server"(Gradle) { 212 | taskNames = ["runObfServer"] 213 | } 214 | } 215 | compiler.javac { 216 | afterEvaluate { 217 | javacAdditionalOptions = "-encoding utf8" 218 | moduleJavacAdditionalOptions = [(project.name + ".main"): tasks.compileJava.options.compilerArgs.collect { '"' + it + '"' }.join(' ')] 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | tasks.named('processIdeaSettings').configure { 226 | dependsOn 'injectTags' 227 | } 228 | 229 | publishing { 230 | publications { 231 | mavenJava(MavenPublication) { 232 | // from components.java 233 | groupId project.group 234 | artifactId project.archivesBaseName 235 | artifact tasks.named('properJar') 236 | artifact tasks.named('properSourcesJar') 237 | artifact tasks.named('javadocJar') 238 | } 239 | } 240 | repositories { 241 | maven { 242 | name = 'CleanroomMaven' 243 | url = 'https://repo.cleanroommc.com/releases' 244 | credentials(PasswordCredentials) 245 | authentication { 246 | basic(BasicAuthentication) 247 | } 248 | } 249 | } 250 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx3G 2 | mod_version=10.6 3 | mod_name=MixinBooter 4 | version_type=release -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanroomMC/MixinBooter/05fc6c7b4b36a714c90eb4cc2f2364c681da0bc8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | // RetroFuturaGradle 5 | name 'GTNH Maven' 6 | url 'http://jenkins.usrv.eu:8081/nexus/content/groups/public/' 7 | allowInsecureProtocol = true 8 | mavenContent { 9 | includeGroup 'com.gtnewhorizons' 10 | includeGroup 'com.gtnewhorizons.retrofuturagradle' 11 | } 12 | } 13 | gradlePluginPortal() 14 | mavenCentral() 15 | mavenLocal() 16 | } 17 | } 18 | 19 | plugins { 20 | // Automatic toolchain provisioning 21 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/Context.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import net.minecraftforge.fml.relauncher.FMLLaunchHandler; 4 | import org.apache.commons.lang3.SystemUtils; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * This class contains loading context for callers 10 | * 11 | * @since 10.0 12 | */ 13 | public final class Context { 14 | 15 | public enum ModLoader { 16 | 17 | FORGE, 18 | CLEANROOM; 19 | 20 | // Something more robust in the future 21 | private static final ModLoader CURRENT = SystemUtils.IS_JAVA_1_8 ? FORGE : CLEANROOM; 22 | 23 | } 24 | 25 | private final String mixinConfig; 26 | private final Collection presentMods; 27 | 28 | public Context(String mixinConfig, Collection presentMods) { 29 | this.mixinConfig = mixinConfig; 30 | this.presentMods = presentMods; 31 | } 32 | 33 | /** 34 | * @return the current mod loader 35 | */ 36 | public ModLoader modLoader() { 37 | return ModLoader.CURRENT; 38 | } 39 | 40 | /** 41 | * @return if the current environment is in-dev 42 | */ 43 | public boolean inDev() { 44 | return FMLLaunchHandler.isDeobfuscatedEnvironment(); 45 | } 46 | 47 | /** 48 | * @return name of the mixin config that is currently being processed 49 | */ 50 | public String mixinConfig() { 51 | return mixinConfig; 52 | } 53 | 54 | /** 55 | *

For early contexts, the list of mods are gathered from culling the classloader 56 | * for any jars that has the mcmod.info file. The mod IDs are obtained from the mcmod.info file. 57 | * This means mostly, if not only coremods are queryable here, 58 | * make sure to test a normal mod's existence in your mixin plugin or in the mixin itself.

59 | * 60 | *

For late contexts, it comes from {@link net.minecraftforge.fml.common.Loader#getActiveModList} 61 | * akin to {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)}

62 | * @param modId to check against the list of present mods in the context 63 | * @return whether the mod is present 64 | */ 65 | public boolean isModPresent(String modId) { 66 | return presentMods.contains(modId); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/IEarlyMixinLoader.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Early mixins are defined as mixins that affects vanilla or forge classes. 7 | * Or technically, classes that can be queried via the current state of {@link net.minecraft.launchwrapper.LaunchClassLoader} 8 | * 9 | * If you want to add mixins that affect mods, use {@link ILateMixinLoader} 10 | * 11 | * Implement this in your {@link net.minecraftforge.fml.relauncher.IFMLLoadingPlugin}. 12 | * Return all early mixin configs you want MixinBooter to queue and send to Mixin library. 13 | */ 14 | public interface IEarlyMixinLoader { 15 | 16 | /** 17 | * @return mixin configurations to be queued and sent to Mixin library. 18 | */ 19 | List getMixinConfigs(); 20 | 21 | /** 22 | * Runs when a mixin config is successfully queued and sent to Mixin library. 23 | * 24 | * @since 10.0 25 | * @param context current context of the loading process. 26 | * @return true if the mixinConfig should be queued, false if it should not. 27 | */ 28 | default boolean shouldMixinConfigQueue(Context context) { 29 | return this.shouldMixinConfigQueue(context.mixinConfig()); 30 | } 31 | 32 | /** 33 | * Runs when a mixin config is successfully queued and sent to Mixin library. 34 | * 35 | * @param mixinConfig mixin config name, queried via {@link IEarlyMixinLoader#getMixinConfigs()}. 36 | * @return true if the mixinConfig should be queued, false if it should not. 37 | */ 38 | default boolean shouldMixinConfigQueue(String mixinConfig) { 39 | return true; 40 | } 41 | 42 | /** 43 | * Runs when a mixin config is successfully queued and sent to Mixin library. 44 | * @since 10.0 45 | * @param context current context of the loading process. 46 | */ 47 | default void onMixinConfigQueued(Context context) { 48 | this.onMixinConfigQueued(context.mixinConfig()); 49 | } 50 | 51 | /** 52 | * Runs when a mixin config is successfully queued and sent to Mixin library. 53 | * @param mixinConfig mixin config name, queried via {@link IEarlyMixinLoader#getMixinConfigs()}. 54 | */ 55 | default void onMixinConfigQueued(String mixinConfig) { } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/ILateMixinLoader.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Late mixins are defined as mixins that affects mod classes. 7 | * Or technically, classes that can be queried via the current state of 8 | * {@link net.minecraft.launchwrapper.LaunchClassLoader} and {@link net.minecraftforge.fml.common.ModClassLoader} 9 | * 10 | * Majority if not all vanilla and forge classes would have been loaded here. 11 | * If you want to add mixins that affect vanilla or forge, use and consult {@link IEarlyMixinLoader} 12 | * 13 | * Implement this in any arbitrary class. Said class will be constructed when mixins are ready to be queued. 14 | * Return all late mixin configs you want MixinBooter to queue and send to Mixin library. 15 | */ 16 | public interface ILateMixinLoader { 17 | 18 | /** 19 | * @return mixin configurations to be queued and sent to Mixin library. 20 | */ 21 | List getMixinConfigs(); 22 | 23 | /** 24 | * Runs when a mixin config is successfully queued and sent to Mixin library. 25 | * 26 | * @since 10.0 27 | * @param context current context of the loading process. 28 | * @return true if the mixinConfig should be queued, false if it should not. 29 | */ 30 | default boolean shouldMixinConfigQueue(Context context) { 31 | return this.shouldMixinConfigQueue(context.mixinConfig()); 32 | } 33 | 34 | /** 35 | * Runs when a mixin config is successfully queued and sent to Mixin library. 36 | * 37 | * @param mixinConfig mixin config name, queried via {@link ILateMixinLoader#getMixinConfigs()}. 38 | * @return true if the mixinConfig should be queued, false if it should not. 39 | */ 40 | default boolean shouldMixinConfigQueue(String mixinConfig) { 41 | return true; 42 | } 43 | 44 | /** 45 | * Runs when a mixin config is successfully queued and sent to Mixin library. 46 | * @since 10.0 47 | * @param context current context of the loading process. 48 | */ 49 | default void onMixinConfigQueued(Context context) { 50 | this.onMixinConfigQueued(context.mixinConfig()); 51 | } 52 | 53 | /** 54 | * Runs when a mixin config is successfully queued and sent to Mixin library. 55 | * @param mixinConfig mixin config name, queried via {@link ILateMixinLoader#getMixinConfigs()}. 56 | */ 57 | default void onMixinConfigQueued(String mixinConfig) { } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/IMixinConfigHijacker.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Hijackers are used to stop certain mixin configurations from ever being applied. 7 | * Usage is similar to {@link IEarlyMixinLoader}, implement it in your coremod class. 8 | * Requested by: @Desoroxxx 9 | * 10 | * @since 9.0 11 | */ 12 | public interface IMixinConfigHijacker { 13 | 14 | /** 15 | * Return a set of mixin config names to not be loaded by the mixin environment. 16 | * 17 | * @since 9.0 18 | */ 19 | Set getHijackedMixinConfigs(); 20 | 21 | /** 22 | * Return a set of mixin config names to not be loaded by the mixin environment. 23 | * 24 | * @since 10.3 25 | * @param context current context of the loading process. Mixin config will be null as it is not applicable. 26 | */ 27 | default Set getHijackedMixinConfigs(Context context) { 28 | return getHijackedMixinConfigs(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/MixinBooterMixinPlugin.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import org.objectweb.asm.tree.ClassNode; 4 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 5 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public class MixinBooterMixinPlugin implements IMixinConfigPlugin { 11 | 12 | @Override 13 | public void onLoad(String mixinPackage) { } 14 | 15 | @Override 16 | public String getRefMapperConfig() { 17 | return null; 18 | } 19 | 20 | @Override 21 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 22 | String version = MixinBooterPlugin.getMinecraftVersion(); 23 | if (mixinClassName.contains("CrashReport")) { 24 | // 1.8.x 25 | return !version.startsWith("1.8."); 26 | } 27 | return true; 28 | } 29 | 30 | @Override 31 | public void acceptTargets(Set myTargets, Set otherTargets) { } 32 | 33 | @Override 34 | public List getMixins() { 35 | return null; 36 | } 37 | 38 | @Override 39 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } 40 | 41 | @Override 42 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/MixinBooterModContainer.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import com.google.common.eventbus.EventBus; 4 | import net.minecraftforge.fml.common.DummyModContainer; 5 | import net.minecraftforge.fml.common.LoadController; 6 | import net.minecraftforge.fml.common.ModMetadata; 7 | import net.minecraftforge.fml.common.versioning.ArtifactVersion; 8 | import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; 9 | import net.minecraftforge.fml.common.versioning.InvalidVersionSpecificationException; 10 | import net.minecraftforge.fml.common.versioning.VersionRange; 11 | 12 | import java.util.Collections; 13 | import java.util.Set; 14 | 15 | public class MixinBooterModContainer extends DummyModContainer { 16 | 17 | public MixinBooterModContainer() { 18 | super(new ModMetadata()); 19 | MixinBooterPlugin.LOGGER.info("Initializing MixinBooter's Mod Container."); 20 | ModMetadata meta = this.getMetadata(); 21 | meta.modId = Tags.MOD_ID; 22 | meta.name = Tags.MOD_NAME; 23 | meta.description = "A mod that provides the Sponge Mixin library, a standard API for mods to load mixins targeting Minecraft and other mods, and associated useful utilities on 1.8 - 1.12.2."; 24 | meta.credits = "Thanks to LegacyModdingMC + Fabric for providing the initial mixin fork."; 25 | meta.version = Tags.VERSION; 26 | meta.logoFile = "/icon.png"; 27 | meta.authorList.add("Rongmario"); 28 | } 29 | 30 | @Override 31 | public boolean registerBus(EventBus bus, LoadController controller) { 32 | bus.register(this); 33 | return true; 34 | } 35 | 36 | @Override 37 | public Set getRequirements() { 38 | try { 39 | if ("1.12.2".equals(MixinBooterPlugin.getMinecraftVersion())) { 40 | try { 41 | return Collections.singleton(new SpongeForgeArtifactVersion()); 42 | } catch (InvalidVersionSpecificationException e) { 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | } catch (Throwable ignored) { } 47 | return Collections.emptySet(); 48 | } 49 | 50 | // Thank you SpongeForge ^_^ 51 | private static class SpongeForgeArtifactVersion extends DefaultArtifactVersion { 52 | 53 | public SpongeForgeArtifactVersion() throws InvalidVersionSpecificationException { 54 | super("spongeforge", VersionRange.createFromVersionSpec("[7.4.8,)")); 55 | } 56 | 57 | @Override 58 | public boolean containsVersion(ArtifactVersion source) { 59 | if (source == this) { 60 | return true; 61 | } 62 | String version = source.getVersionString(); 63 | String[] hyphenSplits = version.split("-"); 64 | if (hyphenSplits.length > 1) { 65 | if (hyphenSplits[hyphenSplits.length - 1].startsWith("RC")) { 66 | version = hyphenSplits[hyphenSplits.length - 2]; 67 | } else { 68 | version = hyphenSplits[hyphenSplits.length - 1]; 69 | } 70 | } 71 | source = new DefaultArtifactVersion(source.getLabel(), version); 72 | return super.containsVersion(source); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/MixinBooterPlugin.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import com.google.gson.*; 4 | import com.llamalad7.mixinextras.MixinExtrasBootstrap; 5 | import net.minecraft.launchwrapper.Launch; 6 | import net.minecraftforge.fml.common.*; 7 | import net.minecraftforge.fml.common.discovery.ASMDataTable; 8 | import net.minecraftforge.fml.common.discovery.ModCandidate; 9 | import net.minecraftforge.fml.relauncher.FMLInjectionData; 10 | import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | import org.spongepowered.asm.launch.GlobalProperties; 14 | import org.spongepowered.asm.launch.MixinBootstrap; 15 | import org.spongepowered.asm.mixin.Mixins; 16 | import org.spongepowered.asm.mixin.ModUtil; 17 | import org.spongepowered.asm.mixin.transformer.Config; 18 | import org.spongepowered.asm.util.PrettyPrinter; 19 | import org.spongepowered.asm.util.asm.ASM; 20 | import zone.rong.mixinbooter.fix.MixinFixer; 21 | 22 | import java.io.InputStreamReader; 23 | import java.lang.reflect.Field; 24 | import java.net.URL; 25 | import java.util.*; 26 | import java.util.function.Supplier; 27 | 28 | @IFMLLoadingPlugin.Name("MixinBooter") 29 | @IFMLLoadingPlugin.SortingIndex(Integer.MIN_VALUE + 1) 30 | public final class MixinBooterPlugin implements IFMLLoadingPlugin { 31 | 32 | public static final Logger LOGGER = LogManager.getLogger("MixinBooter"); 33 | 34 | private static final Map presentJarsToMods = new HashMap<>(); 35 | private static final Set presentMods = new HashSet<>(); 36 | private static final Set unmodifiablePresentMods = Collections.unmodifiableSet(presentMods); 37 | 38 | private static Field modApiManager$dataTable; 39 | 40 | static String getMinecraftVersion() { 41 | return (String) FMLInjectionData.data()[4]; 42 | } 43 | 44 | public MixinBooterPlugin() { 45 | this.addTransformationExclusions(); 46 | this.initialize(); 47 | } 48 | 49 | @Override 50 | public String[] getASMTransformerClass() { 51 | return new String[0]; 52 | } 53 | 54 | @Override 55 | public String getModContainerClass() { 56 | return "zone.rong.mixinbooter.MixinBooterModContainer"; 57 | } 58 | 59 | @Override 60 | public String getSetupClass() { 61 | return null; 62 | } 63 | 64 | @Override 65 | public void injectData(Map data) { 66 | Object coremodList = data.get("coremodList"); 67 | if (coremodList instanceof List) { 68 | Collection earlyLoaders = this.gatherEarlyLoaders((List) coremodList); 69 | this.loadEarlyLoaders(earlyLoaders); 70 | this.recordConfigOwners(); 71 | } else { 72 | throw new RuntimeException("Blackboard property 'coremodList' must be of type List, early loaders were not able to be gathered"); 73 | } 74 | } 75 | 76 | @Override 77 | public String getAccessTransformerClass() { 78 | return null; 79 | } 80 | 81 | private void addTransformationExclusions() { 82 | Launch.classLoader.addTransformerExclusion("scala."); 83 | // Launch.classLoader.addTransformerExclusion("com.llamalad7.mixinextras."); 84 | } 85 | 86 | private void initialize() { 87 | GlobalProperties.put(GlobalProperties.Keys.CLEANROOM_DISABLE_MIXIN_CONFIGS, new HashSet<>()); 88 | 89 | LOGGER.info("Initializing Mixins..."); 90 | MixinBootstrap.init(); 91 | 92 | Mixins.addConfiguration("mixin.mixinbooter.init.json"); 93 | 94 | LOGGER.info("Initializing MixinExtras..."); 95 | this.initMixinExtras(); 96 | 97 | MixinFixer.patchAncientModMixinsLoadingMethod(); 98 | 99 | LOGGER.info("Gathering present mods..."); 100 | this.gatherPresentMods(); 101 | 102 | this.afterAll(); 103 | } 104 | 105 | private void initMixinExtras() { 106 | if (!ASM.isAtLeastVersion(5, 1)) { 107 | Launch.classLoader.registerTransformer("zone.rong.mixinbooter.fix.mixinextras.MixinExtrasFixer"); 108 | } 109 | MixinExtrasBootstrap.init(); 110 | } 111 | 112 | private void afterAll() { 113 | if (unmodifiablePresentMods.contains("spongeforge")) { 114 | LOGGER.info("Registering SpongeForgeFixer transformer to solve issues pertaining SpongeForge."); 115 | Launch.classLoader.registerTransformer("zone.rong.mixinbooter.fix.spongeforge.SpongeForgeFixer"); 116 | // Eagerly load PrettyPrinter class for transformation 117 | new PrettyPrinter(); 118 | // Also apply eagerly loading of Event.class in the EventSubscriptionTransformer 119 | // While technically a Forge bug, it manifests when SpongeForge is installed with Mixin 0.8.5+ 120 | Launch.classLoader.registerTransformer("zone.rong.mixinbooter.fix.forge.EagerlyLoadEventClassTransformer"); 121 | } 122 | } 123 | 124 | private void gatherPresentMods() { 125 | // TODO: Provide versioning for mods? 126 | Gson gson; 127 | try { 128 | gson = new GsonBuilder().setLenient().create(); 129 | } catch (NoSuchMethodError e) { 130 | // Older gsons 131 | gson = new GsonBuilder().create(); 132 | } 133 | try { 134 | Enumeration resources = Launch.classLoader.getResources("mcmod.info"); 135 | while (resources.hasMoreElements()) { 136 | URL url = resources.nextElement(); 137 | String fileName = getJarNameFromResource(url); 138 | if (fileName != null) { 139 | List modIds = parseMcmodInfo(gson, url); 140 | if (!modIds.isEmpty()) { 141 | presentJarsToMods.put(fileName, modIds.get(0)); 142 | } 143 | presentMods.addAll(modIds); 144 | } 145 | } 146 | 147 | URL optifineConfigClass = Launch.classLoader.findResource("Config.class"); 148 | if (optifineConfigClass != null) { 149 | presentJarsToMods.put(getJarNameFromResource(optifineConfigClass), "optifine"); 150 | presentMods.add("optifine"); 151 | } 152 | } catch (Exception e) { 153 | throw new RuntimeException("Failed to gather present mods", e); 154 | } 155 | 156 | logInfo("Finished gathering %d coremods...", unmodifiablePresentMods.size()); 157 | logDebug("Coremods gathered: %s", String.join(", ", unmodifiablePresentMods)); 158 | } 159 | 160 | private String getJarNameFromResource(URL url) { 161 | if (url.getPath().contains("!/")) { 162 | String filePath = url.getPath().split("!/")[0]; 163 | String[] parts = filePath.split("/"); 164 | if (parts.length != 0) { 165 | return parts[parts.length - 1]; 166 | } 167 | } 168 | return null; 169 | } 170 | 171 | private List parseMcmodInfo(Gson gson, URL url) { 172 | try { 173 | List ids = new ArrayList<>(); 174 | JsonElement root = gson.fromJson(new InputStreamReader(url.openStream()), JsonElement.class); 175 | if (root.isJsonArray()) { 176 | for (JsonElement element : root.getAsJsonArray()) { 177 | if (element.isJsonObject()) { 178 | ids.add(element.getAsJsonObject().get("modid").getAsString()); 179 | } 180 | } 181 | } else { 182 | for (JsonElement element : root.getAsJsonObject().get("modList").getAsJsonArray()) { 183 | if (element.isJsonObject()) { 184 | ids.add(element.getAsJsonObject().get("modid").getAsString()); 185 | } 186 | } 187 | } 188 | return ids; 189 | } catch (Throwable t) { 190 | logError("Failed to parse mcmod.info for %s", t, url); 191 | } 192 | return Collections.emptyList(); 193 | } 194 | 195 | private Collection gatherEarlyLoaders(List coremodList) { 196 | Field fmlPluginWrapper$coreModInstance = null; 197 | Set queuedLoaders = new LinkedHashSet<>(); 198 | Collection disabledConfigs = GlobalProperties.get(GlobalProperties.Keys.CLEANROOM_DISABLE_MIXIN_CONFIGS); 199 | Context context = new Context(null, unmodifiablePresentMods); // For hijackers 200 | for (Object coremod : coremodList) { 201 | try { 202 | if (fmlPluginWrapper$coreModInstance == null) { 203 | fmlPluginWrapper$coreModInstance = coremod.getClass().getField("coreModInstance"); 204 | fmlPluginWrapper$coreModInstance.setAccessible(true); 205 | } 206 | Object theMod = fmlPluginWrapper$coreModInstance.get(coremod); 207 | if (theMod instanceof IMixinConfigHijacker) { 208 | IMixinConfigHijacker interceptor = (IMixinConfigHijacker) theMod; 209 | logInfo("Loading config hijacker %s.", interceptor.getClass().getName()); 210 | for (String hijacked : interceptor.getHijackedMixinConfigs(context)) { 211 | disabledConfigs.add(hijacked); 212 | logInfo("%s will hijack the mixin config %s", interceptor.getClass().getName(), hijacked); 213 | } 214 | } 215 | if (theMod instanceof IEarlyMixinLoader) { 216 | queuedLoaders.add((IEarlyMixinLoader) theMod); 217 | } 218 | } catch (Throwable t) { 219 | LOGGER.error("Unexpected error", t); 220 | } 221 | } 222 | return queuedLoaders; 223 | } 224 | 225 | private void loadEarlyLoaders(Collection queuedLoaders) { 226 | for (IEarlyMixinLoader queuedLoader : queuedLoaders) { 227 | logInfo("Loading early loader %s for its mixins.", queuedLoader.getClass().getName()); 228 | try { 229 | for (String mixinConfig : queuedLoader.getMixinConfigs()) { 230 | Context context = new Context(mixinConfig, unmodifiablePresentMods); 231 | if (queuedLoader.shouldMixinConfigQueue(context)) { 232 | logInfo("Adding [%s] mixin configuration.", mixinConfig); 233 | Mixins.addConfiguration(mixinConfig); 234 | queuedLoader.onMixinConfigQueued(context); 235 | } 236 | } 237 | } catch (Throwable t) { 238 | logError("Failed to execute early loader [%s].", t, queuedLoader.getClass().getName()); 239 | } 240 | } 241 | } 242 | 243 | private void recordConfigOwners() { 244 | for (Config config : Mixins.getConfigs()) { 245 | if (!config.getConfig().hasDecoration(ModUtil.OWNER_DECORATOR)) { 246 | config.getConfig().decorate(ModUtil.OWNER_DECORATOR, (Supplier) () -> this.retrieveConfigOwner(config)); 247 | } 248 | } 249 | } 250 | 251 | private String retrieveConfigOwner(Config config) { 252 | if (modApiManager$dataTable == null) { 253 | try { 254 | modApiManager$dataTable = ModAPIManager.class.getDeclaredField("dataTable"); 255 | modApiManager$dataTable.setAccessible(true); 256 | } catch (ReflectiveOperationException e) { 257 | throw new RuntimeException("Unable to reflectively retrieve ModAPIManager#dataTable", e); 258 | } 259 | } 260 | try { 261 | ASMDataTable table = (ASMDataTable) modApiManager$dataTable.get(ModAPIManager.INSTANCE); 262 | if (table != null) { 263 | String pkg = config.getConfig().getMixinPackage(); 264 | pkg = pkg.charAt(pkg.length() - 1) == '.' ? pkg.substring(0, pkg.length() - 1) : pkg; 265 | ModCandidate candidate = table.getCandidatesFor(pkg).stream().findFirst().orElse(null); 266 | if (candidate != null) { 267 | ModContainer container = candidate.getContainedMods().get(0); 268 | if (container != null) { 269 | return container.getModId(); 270 | } 271 | } 272 | } 273 | } catch (IllegalAccessException ignore) { } 274 | URL url = Launch.classLoader.getResource(config.getName()); 275 | if (url != null) { 276 | String jar = this.getJarNameFromResource(url); 277 | if (jar != null) { 278 | String modId = presentJarsToMods.get(jar); 279 | if (modId != null) { 280 | return modId; 281 | } 282 | } 283 | } 284 | return ModUtil.UNKNOWN_OWNER; 285 | } 286 | 287 | /* 288 | * Minecraft 1.8.x uses a beta version of Log4j2 with a slightly different 289 | * API for parameterized logging than ended up in the releases used by 1.12+. 290 | * 291 | * The following methods act as a workaround for that issue while keeping the 292 | * performance conscious "log only if enabled" approach employed by Log4j2 internally. 293 | */ 294 | 295 | @SuppressWarnings("StringConcatenationArgumentToLogCall") 296 | public static void logInfo(String message, Object... params) { 297 | LOGGER.info(String.format(message, params)); 298 | } 299 | 300 | @SuppressWarnings("StringConcatenationArgumentToLogCall") 301 | public static void logError(String message, Throwable t, Object... params) { 302 | LOGGER.error(String.format(message, params), t); 303 | } 304 | 305 | @SuppressWarnings("StringConcatenationArgumentToLogCall") 306 | public static void logDebug(String message, Object... params) { 307 | LOGGER.debug(String.format(message, params)); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/MixinLoader.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Purely a marker annotation. Use it in classes and said classes will be instantiated. 10 | * Make sure there is an empty-arg constructor as LoaderMixin will call newInstance on it. 11 | * Feel free to do any Mixin related things in the constructor. But, most importantly, add (mod mixin) configs there. 12 | * 13 | * @since 4.2 this class was deprecated. 14 | * @since 9.2 this class was reinstated because of Forge not gathering interfaces in 1.8.x. 15 | * When used and paired with {@link ILateMixinLoader}, it will act the same way as it would in a 1.9+ setting. 16 | * Consult the usage of {@link ILateMixinLoader} for mod mixins, and for vanilla/forge mixins, consult the usage of {@link IEarlyMixinLoader} 17 | */ 18 | @Target(ElementType.TYPE) 19 | @Retention(RetentionPolicy.CLASS) 20 | public @interface MixinLoader { } 21 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/fix/MixinFixer.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.fix; 2 | 3 | import net.minecraft.launchwrapper.Launch; 4 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 5 | import org.spongepowered.asm.mixin.transformer.ClassInfo; 6 | import sun.misc.Unsafe; 7 | import zone.rong.mixinbooter.MixinBooterPlugin; 8 | 9 | import java.lang.reflect.Field; 10 | import java.util.AbstractList; 11 | import java.util.Collection; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | public class MixinFixer { 16 | 17 | static Unsafe unsafe; 18 | static boolean registered = false; 19 | static Set queuedLateMixinConfigs = new HashSet<>(); 20 | 21 | /** 22 | * For internal usage 23 | */ 24 | public static Set retrieveLateMixinConfigs() { 25 | Set ret = queuedLateMixinConfigs; 26 | queuedLateMixinConfigs = null; 27 | return ret; 28 | } 29 | 30 | public static void patchAncientModMixinsLoadingMethod() { 31 | if (registered) { 32 | return; 33 | } 34 | registered = true; 35 | ClassInfo.registerCallback(ci -> { 36 | if (!ci.isMixin() && "net/minecraftforge/fml/common/Loader".equals(ci.getName())) { 37 | try { 38 | // OpenJ9 Compatibility 39 | Field unsafe$theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); 40 | unsafe$theUnsafe.setAccessible(true); 41 | unsafe = (Unsafe) unsafe$theUnsafe.get(null); 42 | Field classInfo$mixinsField = ClassInfo.class.getDeclaredField("mixins"); 43 | classInfo$mixinsField.setAccessible(true); 44 | unsafe.putObject(ci, unsafe.objectFieldOffset(classInfo$mixinsField), new NotifiableMixinSet()); 45 | } catch (ReflectiveOperationException e) { 46 | throw new RuntimeException("Unable to patch for compatibility with older mixin mods", e); 47 | } 48 | } 49 | }); 50 | } 51 | 52 | private static class NotifiableMixinSet extends HashSet { 53 | 54 | private static Field mixinInfo$targetClassNames; 55 | static long mixinInfo$targetClassNames$offset = 0L; 56 | 57 | @Override 58 | public boolean add(IMixinInfo mixinInfo) { 59 | if (mixinInfo$targetClassNames == null) { 60 | try { 61 | mixinInfo$targetClassNames = mixinInfo.getClass().getDeclaredField("targetClassNames"); 62 | mixinInfo$targetClassNames.setAccessible(true); 63 | mixinInfo$targetClassNames$offset = unsafe.objectFieldOffset(mixinInfo$targetClassNames); 64 | } catch (NoSuchFieldException e) { 65 | throw new RuntimeException("Unable to patch for compatibility with older mixin mods", e); 66 | } 67 | } 68 | switch (mixinInfo.getConfig().getName()) { 69 | // Integrated Proxy compatibility 70 | case "mixins.integrated_proxy.loader.json": 71 | MixinFixer.queuedLateMixinConfigs.add("mixins.integrated_proxy.mod.json"); 72 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 73 | return true; 74 | // Just Enough IDs/Roughly Enough IDs compatibility 75 | case "mixins.jeid.init.json": 76 | MixinFixer.queuedLateMixinConfigs.add("mixins.jeid.modsupport.json"); 77 | MixinFixer.queuedLateMixinConfigs.add("mixins.jeid.twilightforest.json"); 78 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 79 | return true; 80 | // DJ2 Addons compatibility 81 | case "mixins.dj2addons.bootstrap.json": 82 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.def.api.json"); 83 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.def.custom.json"); 84 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.def.optimizations.json"); 85 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.def.patches.json"); 86 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.def.tweaks.json"); 87 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 88 | correctingDj2Addons(); 89 | return true; 90 | case "mixins.dj2addons.init.json": // Backwards Compat 91 | MixinFixer.queuedLateMixinConfigs.add("mixins.dj2addons.json"); 92 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 93 | correctingDj2Addons(); 94 | return true; 95 | case "mixins.thaumicfixes.init.json": 96 | MixinFixer.queuedLateMixinConfigs.add("mixins.thaumicfixes.modsupport.json"); 97 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 98 | return true; 99 | case "mixins.loader.json": 100 | switch (mixinInfo.getConfig().getMixinPackage()) { 101 | // ErebusFix compatibility 102 | case "noobanidus.mods.erebusfix.mixins.": 103 | MixinFixer.queuedLateMixinConfigs.add("mixins.erebusfix.json"); 104 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 105 | return true; 106 | // Uncrafting Blacklist compatibility 107 | case "doomanidus.mods.uncraftingblacklist.mixins.": 108 | MixinFixer.queuedLateMixinConfigs.add("mixins.uncraftingblacklist.json"); 109 | unsafe.putObject(mixinInfo, mixinInfo$targetClassNames$offset, new EmptyAbsorbingList()); 110 | return true; 111 | } 112 | } 113 | return super.add(mixinInfo); 114 | } 115 | 116 | private void correctingDj2Addons() { 117 | // If you're bored and want to be enlightened, start here: 118 | // https://discord.com/channels/926486493562814515/926783373232447509/1168057816691507260 119 | try { 120 | // New (unreleased) 121 | Class.forName("btpos.dj2addons.common.CoreInfo", true, Launch.classLoader).getMethod("onLoadCore").invoke(null); 122 | } catch (ReflectiveOperationException e1) { 123 | try { 124 | // CurseForge release (older) 125 | Class.forName("org.btpos.dj2addons.core.DJ2AddonsCore", true, Launch.classLoader).getMethod("onLoadCore").invoke(null); 126 | } catch (ClassNotFoundException | NoClassDefFoundError ignored) { 127 | // Skip when no classes are found, that cannot be the case unless an old dj2addons is installed 128 | } catch (ReflectiveOperationException e2) { 129 | MixinBooterPlugin.LOGGER.fatal("DJ2Addons compatibility patch failed.", e2); 130 | } 131 | } 132 | } 133 | 134 | } 135 | 136 | private static class EmptyAbsorbingList extends AbstractList { 137 | 138 | @Override 139 | public boolean addAll(Collection c) { 140 | return true; 141 | } 142 | 143 | @Override 144 | public String get(int index) { 145 | return null; 146 | } 147 | 148 | @Override 149 | public int size() { 150 | return 0; 151 | } 152 | 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/fix/forge/EagerlyLoadEventClassTransformer.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.fix.forge; 2 | 3 | import net.minecraft.launchwrapper.IClassTransformer; 4 | import org.objectweb.asm.ClassReader; 5 | import org.objectweb.asm.ClassWriter; 6 | import org.objectweb.asm.Opcodes; 7 | import org.objectweb.asm.tree.*; 8 | 9 | import java.util.Iterator; 10 | 11 | public class EagerlyLoadEventClassTransformer implements IClassTransformer, Opcodes { 12 | 13 | @Override 14 | public byte[] transform(String name, String transformedName, byte[] classBytes) { 15 | if ("$wrapper.net.minecraftforge.fml.common.asm.transformers.EventSubscriptionTransformer".equals(name)) { 16 | return this.eagerlyLoadEventClass(classBytes); 17 | } 18 | return classBytes; 19 | } 20 | 21 | private byte[] eagerlyLoadEventClass(byte[] classBytes) { 22 | ClassNode node = new ClassNode(); 23 | ClassReader reader = new ClassReader(classBytes); 24 | reader.accept(node, 0); 25 | 26 | for (MethodNode method : node.methods) { 27 | if ("".equals(method.name)) { 28 | Iterator iterator = method.instructions.iterator(); 29 | while (iterator.hasNext()) { 30 | AbstractInsnNode instruction = iterator.next(); 31 | if (instruction.getOpcode() == RETURN) { 32 | InsnList instructions = new InsnList(); 33 | instructions.add(new TypeInsnNode(NEW, "net/minecraftforge/fml/common/eventhandler/Event")); 34 | instructions.add(new InsnNode(DUP)); 35 | instructions.add(new MethodInsnNode(INVOKESPECIAL, "net/minecraftforge/fml/common/eventhandler/Event", "", "()V", false)); 36 | method.instructions.insert(instruction.getPrevious(), instructions); 37 | break; 38 | } 39 | } 40 | break; 41 | } 42 | } 43 | 44 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 45 | node.accept(writer); 46 | return writer.toByteArray(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/fix/mixinextras/MixinExtrasFixer.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.fix.mixinextras; 2 | 3 | import net.minecraft.launchwrapper.IClassTransformer; 4 | import org.objectweb.asm.ClassReader; 5 | import org.objectweb.asm.ClassWriter; 6 | import org.objectweb.asm.Handle; 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import java.util.ListIterator; 11 | 12 | public class MixinExtrasFixer implements IClassTransformer, Opcodes { 13 | 14 | public static Handle redirect(int tag, String owner, String name, String desc, boolean itf) { 15 | return new Handle(tag, owner, name, desc); 16 | } 17 | 18 | @Override 19 | public byte[] transform(String name, String transformedName, byte[] classBytes) { 20 | switch (name) { 21 | case "com.llamalad7.mixinextras.utils.ASMUtils": 22 | case "com.llamalad7.mixinextras.utils.OperationUtils": 23 | case "com.llamalad7.mixinextras.utils.TypeUtils": 24 | case "com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils": 25 | return this.fixHandleInstantiation(classBytes); 26 | } 27 | return classBytes; 28 | } 29 | 30 | private byte[] fixHandleInstantiation(byte[] classBytes) { 31 | ClassNode node = new ClassNode(); 32 | ClassReader reader = new ClassReader(classBytes); 33 | reader.accept(node, 0); 34 | 35 | for (MethodNode method : node.methods) { 36 | ListIterator iterator = method.instructions.iterator(); 37 | boolean foundNew = false; 38 | boolean foundDup = false; 39 | while (iterator.hasNext()) { 40 | AbstractInsnNode instruction = iterator.next(); 41 | if (!foundNew && !foundDup && instruction.getOpcode() == NEW && 42 | "org/objectweb/asm/Handle".equals(((TypeInsnNode) instruction).desc)) { 43 | foundNew = true; 44 | iterator.remove(); 45 | } else if (foundNew && instruction.getOpcode() == DUP) { 46 | foundNew = false; 47 | foundDup = true; 48 | iterator.remove(); 49 | } else if (!foundNew && foundDup && instruction.getOpcode() == INVOKESPECIAL && 50 | "org/objectweb/asm/Handle".equals(((MethodInsnNode) instruction).owner)) { 51 | iterator.set(new MethodInsnNode(INVOKESTATIC, "zone/rong/mixinbooter/fix/mixinextras/MixinExtrasFixer", "redirect", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lorg/objectweb/asm/Handle;")); 52 | } 53 | } 54 | } 55 | 56 | ClassWriter writer = new ClassWriter(0); 57 | node.accept(writer); 58 | return writer.toByteArray(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/fix/spongeforge/SpongeForgeFixer.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.fix.spongeforge; 2 | 3 | import net.minecraft.launchwrapper.IClassTransformer; 4 | import net.minecraft.launchwrapper.Launch; 5 | import net.minecraft.launchwrapper.LaunchClassLoader; 6 | import org.objectweb.asm.*; 7 | import org.objectweb.asm.tree.*; 8 | import zone.rong.mixinbooter.MixinBooterPlugin; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.Set; 12 | 13 | public class SpongeForgeFixer implements IClassTransformer, Opcodes { 14 | 15 | public SpongeForgeFixer() { 16 | exempt(); 17 | } 18 | 19 | @Override 20 | public byte[] transform(String name, String transformedName, byte[] classBytes) { 21 | if ("org.spongepowered.asm.util.PrettyPrinter".equals(name)) { 22 | MixinBooterPlugin.LOGGER.info("Transforming PrettyPrinter to include old removed methods for SpongeForge."); 23 | return transformPrettyPrinter(classBytes); 24 | } 25 | return classBytes; 26 | } 27 | 28 | private void exempt() { 29 | try { 30 | Field launchClassLoader$classLoaderExceptions = LaunchClassLoader.class.getDeclaredField("classLoaderExceptions"); 31 | launchClassLoader$classLoaderExceptions.setAccessible(true); 32 | ((Set) launchClassLoader$classLoaderExceptions.get(Launch.classLoader)).remove("org.spongepowered.asm.util."); 33 | } catch (ReflectiveOperationException e) { 34 | MixinBooterPlugin.LOGGER.fatal("Cannot exempt org.spongepowered.asm.util. package from being excluded by the class loader," + 35 | "this will impact SpongeForge from working properly!", e); 36 | } 37 | } 38 | 39 | private byte[] transformPrettyPrinter(byte[] classBytes) { 40 | ClassNode node = new ClassNode(); 41 | ClassReader reader = new ClassReader(classBytes); 42 | reader.accept(node, 0); 43 | 44 | final String log4jLogger = "org/apache/logging/log4j/Logger"; 45 | final String log4jLevel = "org/apache/logging/log4j/Level"; 46 | final String prettyPrinter = "org/spongepowered/asm/util/PrettyPrinter"; 47 | final String stringType = "Ljava/lang/String;"; 48 | final String log4jLoggerType = "L" + log4jLogger + ";"; 49 | final String log4jLevelType = "L" + log4jLevel + ";"; 50 | final String prettyPrinterType = "L" + prettyPrinter + ";"; 51 | 52 | // public PrettyPrinter trace(org.apache.logging.log4j.Level level) { 53 | // return this.trace(PrettyPrinter.getDefaultLoggerName(), level); 54 | // } 55 | MethodVisitor method = node.visitMethod(ACC_PUBLIC, "trace", "(" + log4jLevelType + ")" + prettyPrinterType, null, null); 56 | method.visitCode(); 57 | method.visitVarInsn(ALOAD, 0); 58 | method.visitMethodInsn(INVOKESTATIC, prettyPrinter, "getDefaultLoggerName", "()" + stringType, false); 59 | method.visitVarInsn(ALOAD, 1); 60 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(" + stringType + log4jLevelType + ")" + prettyPrinterType, false); 61 | method.visitInsn(ARETURN); 62 | method.visitMaxs(2, 2); 63 | method.visitEnd(); 64 | 65 | // public PrettyPrinter trace(String logger, org.apache.logging.log4j.Level level) { 66 | // return this.trace(System.err, LogManager.getLogger(logger), level); 67 | // } 68 | method = node.visitMethod(ACC_PUBLIC, "trace", "(" + stringType + log4jLevelType + ")" + prettyPrinterType, null, null); 69 | method.visitCode(); 70 | method.visitVarInsn(ALOAD, 0); 71 | method.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); 72 | method.visitVarInsn(ALOAD, 1); 73 | method.visitMethodInsn(INVOKESTATIC, "org/apache/logging/log4j/LogManager", "getLogger", "(" + stringType + ")" + log4jLoggerType, false); 74 | method.visitVarInsn(ALOAD, 2); 75 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 76 | method.visitInsn(ARETURN); 77 | method.visitMaxs(6, 3); 78 | method.visitEnd(); 79 | 80 | // public PrettyPrinter trace(org.apache.logging.log4j.Logger logger) { 81 | // return this.trace(System.err, logger); 82 | // } 83 | method = node.visitMethod(ACC_PUBLIC, "trace", "(" + log4jLoggerType + ")" + prettyPrinterType, null, null); 84 | method.visitCode(); 85 | method.visitVarInsn(ALOAD, 0); 86 | method.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); 87 | method.visitVarInsn(ALOAD, 1); 88 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + ")" + prettyPrinterType, false); 89 | method.visitInsn(ARETURN); 90 | method.visitMaxs(4, 2); 91 | method.visitEnd(); 92 | 93 | // public PrettyPrinter trace(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level) { 94 | // return this.trace(System.err, logger, level); 95 | // } 96 | method = node.visitMethod(ACC_PUBLIC, "trace", "(" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, null, null); 97 | method.visitCode(); 98 | method.visitVarInsn(ALOAD, 0); 99 | method.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); 100 | method.visitVarInsn(ALOAD, 1); 101 | method.visitVarInsn(ALOAD, 2); 102 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 103 | method.visitInsn(ARETURN); 104 | method.visitMaxs(5, 3); 105 | method.visitEnd(); 106 | 107 | // public PrettyPrinter trace(PrintStream stream, org.apache.logging.log4j.Level level) { 108 | // return this.trace(stream, PrettyPrinter.getDefaultLoggerName(), level); 109 | // } 110 | method = node.visitMethod(ACC_PUBLIC, "trace", "(Ljava/io/PrintStream;" + log4jLevelType + ")" + prettyPrinterType, null, null); 111 | method.visitCode(); 112 | method.visitVarInsn(ALOAD, 0); 113 | method.visitVarInsn(ALOAD, 1); 114 | method.visitMethodInsn(INVOKESTATIC, prettyPrinter, "getDefaultLoggerName", "()" + stringType, false); 115 | method.visitVarInsn(ALOAD, 2); 116 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + stringType + log4jLevelType + ")" + prettyPrinterType, false); 117 | method.visitInsn(ARETURN); 118 | method.visitMaxs(5, 3); 119 | method.visitEnd(); 120 | 121 | // public PrettyPrinter trace(PrintStream stream, String logger, org.apache.logging.log4j.Level level) { 122 | // return this.trace(stream, LogManager.getLogger(logger), level); 123 | // } 124 | method = node.visitMethod(ACC_PUBLIC, "trace", "(Ljava/io/PrintStream;" + stringType + log4jLevelType + ")" + prettyPrinterType, null, null); 125 | method.visitCode(); 126 | method.visitVarInsn(ALOAD, 0); 127 | method.visitVarInsn(ALOAD, 1); 128 | method.visitVarInsn(ALOAD, 2); 129 | method.visitMethodInsn(INVOKESTATIC, "org/apache/logging/log4j/LogManager", "getLogger", "(" + stringType + ")" + log4jLoggerType, false); 130 | method.visitVarInsn(ALOAD, 3); 131 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 132 | method.visitInsn(ARETURN); 133 | method.visitMaxs(6, 4); 134 | method.visitEnd(); 135 | 136 | // public PrettyPrinter trace(PrintStream stream, org.apache.logging.log4j.Logger logger) { 137 | // return this.trace(stream, logger, org.apache.logging.log4j.Level.DEBUG); 138 | // } 139 | method = node.visitMethod(ACC_PUBLIC, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + ")" + prettyPrinterType, null, null); 140 | method.visitCode(); 141 | method.visitVarInsn(ALOAD, 0); 142 | method.visitVarInsn(ALOAD, 1); 143 | method.visitVarInsn(ALOAD, 2); 144 | method.visitFieldInsn(GETSTATIC, log4jLevel, "DEBUG", log4jLevelType); 145 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 146 | method.visitInsn(ARETURN); 147 | method.visitMaxs(5, 3); 148 | method.visitEnd(); 149 | 150 | // public PrettyPrinter trace(PrintStream stream, org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level) { 151 | // this.log(logger, level); 152 | // this.print(stream); 153 | // return this; 154 | // } 155 | method = node.visitMethod(ACC_PUBLIC, "trace", "(Ljava/io/PrintStream;" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, null, null); 156 | method.visitCode(); 157 | method.visitVarInsn(ALOAD, 0); 158 | method.visitVarInsn(ALOAD, 2); 159 | method.visitVarInsn(ALOAD, 3); 160 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "log", "(" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 161 | method.visitVarInsn(ALOAD, 0); 162 | method.visitVarInsn(ALOAD, 1); 163 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "print", "(Ljava/io/PrintStream;)" + prettyPrinterType, false); 164 | method.visitVarInsn(ALOAD, 0); 165 | method.visitInsn(ARETURN); 166 | method.visitMaxs(4, 4); 167 | method.visitEnd(); 168 | 169 | // public PrettyPrinter log(org.apache.logging.log4j.Logger logger) { 170 | // return this.log(logger, org.apache.logging.log4j.Level.INFO); 171 | // } 172 | method = node.visitMethod(ACC_PUBLIC, "log", "(" + log4jLoggerType + ")" + prettyPrinterType, null, null); 173 | method.visitCode(); 174 | method.visitVarInsn(ALOAD, 0); 175 | method.visitVarInsn(ALOAD, 1); 176 | method.visitFieldInsn(GETSTATIC, log4jLevel, "INFO", log4jLevelType); 177 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "log", "(" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 178 | method.visitInsn(ARETURN); 179 | method.visitMaxs(4, 2); 180 | method.visitEnd(); 181 | 182 | // public PrettyPrinter log(org.apache.logging.log4j.Level level) { 183 | // return this.log(LogManager.getLogger(PrettyPrinter.getDefaultLoggerName()), level); 184 | // } 185 | method = node.visitMethod(ACC_PUBLIC, "log", "(" + log4jLevelType + ")" + prettyPrinterType, null, null); 186 | method.visitCode(); 187 | method.visitVarInsn(ALOAD, 0); 188 | method.visitMethodInsn(INVOKESTATIC, prettyPrinter, "getDefaultLoggerName", "()" + stringType, false); 189 | method.visitMethodInsn(INVOKESTATIC, "org/apache/logging/log4j/LogManager", "getLogger", "(" + stringType + ")" + log4jLoggerType, false); 190 | method.visitVarInsn(ALOAD, 1); 191 | method.visitMethodInsn(INVOKEVIRTUAL, prettyPrinter, "log", "(" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, false); 192 | method.visitInsn(ARETURN); 193 | method.visitMaxs(5, 2); 194 | method.visitEnd(); 195 | 196 | // public PrettyPrinter log(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level) { 197 | // this.updateWidth(); 198 | // this.logSpecial(logger, level, this.horizontalRule); 199 | // for (Object line : this.lines) { 200 | // if (line instanceof PrettyPrinter.ISpecialEntry) { 201 | // this.logSpecial(logger, level, (PrettyPrinter.ISpecialEntry) line); 202 | // } else { 203 | // this.logString(logger, level, line.toString()); 204 | // } 205 | // } 206 | // this.logSpecial(logger, level, this.horizontalRule); 207 | // return this; 208 | // } 209 | method = node.visitMethod(ACC_PUBLIC, "log", "(" + log4jLoggerType + log4jLevelType + ")" + prettyPrinterType, null, null); 210 | method.visitCode(); 211 | method.visitVarInsn(ALOAD, 0); 212 | method.visitMethodInsn(INVOKESPECIAL, prettyPrinter, "updateWidth", "()V", false); 213 | method.visitVarInsn(ALOAD, 0); 214 | method.visitVarInsn(ALOAD, 1); 215 | method.visitVarInsn(ALOAD, 2); 216 | method.visitVarInsn(ALOAD, 0); 217 | method.visitFieldInsn(GETFIELD, prettyPrinter, "horizontalRule", "L" + prettyPrinter + "$HorizontalRule;"); 218 | method.visitMethodInsn(INVOKESPECIAL, prettyPrinter, "logSpecial", "(" + log4jLoggerType + log4jLevelType + "L" + prettyPrinter + "$ISpecialEntry;" + ")V", false); 219 | method.visitVarInsn(ALOAD, 0); 220 | method.visitFieldInsn(GETFIELD, prettyPrinter, "lines", "Ljava/util/List;"); 221 | method.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true); 222 | method.visitVarInsn(ASTORE, 3); 223 | Label loopLabel = new Label(); 224 | method.visitLabel(loopLabel); 225 | method.visitFrame(F_APPEND, 1, new Object[] { "java/util/Iterator" }, 0, null); 226 | method.visitVarInsn(ALOAD, 3); 227 | method.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true); 228 | Label endLabel1 = new Label(); 229 | method.visitJumpInsn(IFEQ, endLabel1); 230 | method.visitVarInsn(ALOAD, 3); 231 | method.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true); 232 | method.visitVarInsn(ASTORE, 4); 233 | method.visitVarInsn(ALOAD, 4); 234 | method.visitTypeInsn(INSTANCEOF, prettyPrinter + "$ISpecialEntry"); 235 | Label elseLabel = new Label(); 236 | method.visitJumpInsn(IFEQ, elseLabel); 237 | method.visitVarInsn(ALOAD, 0); 238 | method.visitVarInsn(ALOAD, 1); 239 | method.visitVarInsn(ALOAD, 2); 240 | method.visitVarInsn(ALOAD, 4); 241 | method.visitTypeInsn(CHECKCAST, prettyPrinter + "$ISpecialEntry"); 242 | method.visitMethodInsn(INVOKESPECIAL, prettyPrinter, "logSpecial", "(" + log4jLoggerType + log4jLevelType + "L" + prettyPrinter + "$ISpecialEntry;" + ")V", false); 243 | Label frameLabel = new Label(); 244 | method.visitJumpInsn(GOTO, frameLabel); 245 | method.visitLabel(elseLabel); 246 | method.visitFrame(F_APPEND, 1, new Object[] { "java/lang/Object" }, 0, null); 247 | method.visitVarInsn(ALOAD, 0); 248 | method.visitVarInsn(ALOAD, 1); 249 | method.visitVarInsn(ALOAD, 2); 250 | method.visitVarInsn(ALOAD, 4); 251 | method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()" + stringType, false); 252 | method.visitMethodInsn(INVOKESPECIAL, prettyPrinter, "logString", "(" + log4jLoggerType + log4jLevelType + stringType + ")V", false); 253 | method.visitLabel(frameLabel); 254 | method.visitFrame(F_CHOP, 1, null, 0, null); 255 | method.visitJumpInsn(GOTO, loopLabel); 256 | method.visitLabel(endLabel1); 257 | method.visitFrame(F_CHOP, 1, null, 0, null); 258 | method.visitVarInsn(ALOAD, 0); 259 | method.visitVarInsn(ALOAD, 1); 260 | method.visitVarInsn(ALOAD, 2); 261 | method.visitVarInsn(ALOAD, 0); 262 | method.visitFieldInsn(GETFIELD, prettyPrinter, "horizontalRule", "L" + prettyPrinter + "$HorizontalRule;"); 263 | method.visitMethodInsn(INVOKESPECIAL, prettyPrinter, "logSpecial", "(" + log4jLoggerType + log4jLevelType + "L" + prettyPrinter + "$ISpecialEntry;" + ")V", false); 264 | method.visitVarInsn(ALOAD, 0); 265 | method.visitInsn(ARETURN); 266 | method.visitMaxs(4, 5); 267 | method.visitEnd(); 268 | 269 | // private void logSpecial(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level, PrettyPrinter.ISpecialEntry line) { 270 | // logger.log(level, "/*{}*/", line.toString()); 271 | // } 272 | method = node.visitMethod(ACC_PRIVATE, "logSpecial", "(" + log4jLoggerType + log4jLevelType + "L" + prettyPrinter + "$ISpecialEntry;" + ")V", null, null); 273 | method.visitCode(); 274 | method.visitVarInsn(ALOAD, 1); 275 | method.visitVarInsn(ALOAD, 2); 276 | method.visitLdcInsn("/*{}*/"); 277 | method.visitInsn(ICONST_1); 278 | method.visitTypeInsn(ANEWARRAY, "java/lang/Object"); 279 | method.visitInsn(DUP); 280 | method.visitInsn(ICONST_0); 281 | method.visitVarInsn(ALOAD, 3); 282 | method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()" + stringType, false); 283 | method.visitInsn(Opcodes.AASTORE); 284 | method.visitMethodInsn(INVOKEINTERFACE, log4jLogger, "log", "(" + log4jLevelType + stringType + "[Ljava/lang/Object;)V", true); 285 | method.visitInsn(RETURN); 286 | method.visitMaxs(5, 4); 287 | method.visitEnd(); 288 | 289 | // private void logString(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level, String line) { 290 | // if (line != null) { 291 | // logger.log(level, "/* {} */", String.format("%-" + this.width + "s", line)); 292 | // } 293 | // } 294 | method = node.visitMethod(ACC_PRIVATE, "logString", "(" + log4jLoggerType + log4jLevelType + stringType + ")V", null, null); 295 | method.visitCode(); 296 | Label endLabel2 = new Label(); 297 | method.visitVarInsn(ALOAD, 3); 298 | method.visitJumpInsn(IFNULL, endLabel2); 299 | method.visitVarInsn(ALOAD, 1); 300 | method.visitVarInsn(ALOAD, 2); 301 | method.visitLdcInsn("/* {} */"); 302 | method.visitInsn(ICONST_1); 303 | method.visitTypeInsn(ANEWARRAY, "java/lang/Object"); 304 | method.visitInsn(DUP); 305 | method.visitInsn(ICONST_0); 306 | method.visitTypeInsn(NEW, "java/lang/StringBuilder"); 307 | method.visitInsn(DUP); 308 | method.visitLdcInsn("%-"); 309 | method.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(" + stringType + ")V", false); 310 | method.visitVarInsn(ALOAD, 0); 311 | method.visitFieldInsn(GETFIELD, prettyPrinter, "width", "I"); 312 | method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); 313 | method.visitLdcInsn("s"); 314 | method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(" + stringType + ")Ljava/lang/StringBuilder;", false); 315 | method.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()" + stringType, false); 316 | method.visitInsn(ICONST_1); 317 | method.visitTypeInsn(ANEWARRAY, "java/lang/Object"); 318 | method.visitInsn(DUP); 319 | method.visitInsn(ICONST_0); 320 | method.visitVarInsn(ALOAD, 3); 321 | method.visitInsn(Opcodes.AASTORE); 322 | method.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format", "(" + stringType + "[Ljava/lang/Object;)" + stringType, false); 323 | method.visitInsn(Opcodes.AASTORE); 324 | method.visitMethodInsn(INVOKEINTERFACE, log4jLogger, "log", "(" + log4jLevelType + stringType + "[Ljava/lang/Object;)V", true); 325 | method.visitLabel(endLabel2); 326 | method.visitFrame(F_SAME, 0, null, 0, null); 327 | method.visitInsn(RETURN); 328 | method.visitMaxs(11, 4); 329 | method.visitEnd(); 330 | 331 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 332 | node.accept(writer); 333 | return writer.toByteArray(); 334 | } 335 | 336 | } 337 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/mixin/CrashReportMixin.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import net.minecraft.crash.CrashReport; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.ModUtil; 7 | import org.spongepowered.asm.mixin.Unique; 8 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 12 | import org.spongepowered.asm.mixin.transformer.ClassInfo; 13 | import zone.rong.mixinbooter.MixinBooterPlugin; 14 | 15 | import java.lang.reflect.Field; 16 | import java.util.*; 17 | 18 | /** 19 | * Mixin that allows CrashReports to be appended with mixin information. 20 | * Any classes that have mixins enacted to it within the stacktrace will be shown just after the stacktrace section. 21 | */ 22 | @Mixin(CrashReport.class) 23 | public class CrashReportMixin { 24 | 25 | @Inject(method = "getCauseStackTraceOrString", at = @At("RETURN"), cancellable = true) 26 | private void afterStackTracePopulation(CallbackInfoReturnable cir, @Local(ordinal = 0) Throwable throwable) { 27 | try { 28 | Field classInfo$mixins = ClassInfo.class.getDeclaredField("mixins"); 29 | classInfo$mixins.setAccessible(true); 30 | Map classes = new LinkedHashMap<>(); 31 | while (throwable != null) { 32 | if (throwable instanceof NoClassDefFoundError) { 33 | ClassInfo classInfo = ClassInfo.fromCache(throwable.getMessage()); 34 | if (classInfo != null) { 35 | classes.put(throwable.getMessage(), classInfo); 36 | } 37 | } 38 | StackTraceElement[] stacktrace = throwable.getStackTrace(); 39 | for (StackTraceElement stackTraceElement : stacktrace) { 40 | String className = stackTraceElement.getClassName().replace('.', '/'); 41 | if (classes.containsKey(className)) { 42 | ClassInfo classInfo = ClassInfo.fromCache(className); 43 | while (classInfo != null) { 44 | classes.put(className, classInfo); 45 | className = classInfo.getSuperName(); 46 | if (className == null || className.isEmpty() || "java/lang/Object".equals(className)) { 47 | break; 48 | } 49 | classInfo = classInfo.getSuperClass(); 50 | } 51 | } 52 | } 53 | throwable = throwable.getCause(); 54 | } 55 | if (classes.isEmpty()) { 56 | cir.setReturnValue(cir.getReturnValue() + "\nNo Mixin Metadata is found in the Stacktrace.\n"); 57 | } else { 58 | StringBuilder mixinMetadataBuilder = new StringBuilder("\n(MixinBooter) Mixins in Stacktrace:"); 59 | boolean addedMetadata = false; 60 | for (Map.Entry entry : classes.entrySet()) { 61 | addedMetadata |= mixinbooter$findAndAddMixinMetadata(mixinMetadataBuilder, entry.getKey(), entry.getValue()); 62 | } 63 | if (addedMetadata) { 64 | cir.setReturnValue(cir.getReturnValue() + mixinMetadataBuilder); 65 | } else { 66 | cir.setReturnValue(cir.getReturnValue() + "\nNo Mixin Metadata is found in the Stacktrace.\n"); 67 | } 68 | } 69 | } catch (Throwable t) { 70 | MixinBooterPlugin.LOGGER.fatal("Unable to gather mixin metadata from the stacktrace", t); 71 | cir.setReturnValue(cir.getReturnValue() + "\nFailed to find Mixin Metadata in Stacktrace due to exception: " + t); 72 | } 73 | } 74 | 75 | @Unique 76 | private boolean mixinbooter$findAndAddMixinMetadata(StringBuilder mixinMetadataBuilder, String className, ClassInfo classInfo) { 77 | Set mixinInfos = classInfo.getApplicableMixins(); 78 | if (!mixinInfos.isEmpty()) { 79 | mixinMetadataBuilder.append("\n\t"); 80 | mixinMetadataBuilder.append(className); 81 | mixinMetadataBuilder.append(':'); 82 | for (IMixinInfo mixinInfo : mixinInfos) { 83 | mixinMetadataBuilder.append("\n\t\t"); 84 | mixinMetadataBuilder.append(mixinInfo.getClassName()); 85 | mixinMetadataBuilder.append(" ("); 86 | mixinMetadataBuilder.append(mixinInfo.getConfig()); 87 | mixinMetadataBuilder.append(") ["); 88 | mixinMetadataBuilder.append(ModUtil.owner(mixinInfo.getConfig())); 89 | mixinMetadataBuilder.append("]"); 90 | } 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/zone/rong/mixinbooter/mixin/LoadControllerMixin.java: -------------------------------------------------------------------------------- 1 | package zone.rong.mixinbooter.mixin; 2 | 3 | import net.minecraftforge.fml.common.*; 4 | import net.minecraftforge.fml.common.discovery.ASMDataTable; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.MixinEnvironment; 7 | import org.spongepowered.asm.mixin.Mixins; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.extensibility.IMixinProcessor; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.transformer.IMixinTransformer; 14 | import org.spongepowered.asm.mixin.transformer.Proxy; 15 | import org.spongepowered.asm.service.MixinService; 16 | import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper; 17 | import zone.rong.mixinbooter.*; 18 | import zone.rong.mixinbooter.fix.MixinFixer; 19 | 20 | import java.lang.reflect.Field; 21 | import java.lang.reflect.Method; 22 | import java.util.Collection; 23 | import java.util.HashSet; 24 | import java.util.Set; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * Mixin that allows us to load "late" mixins for mods. 29 | */ 30 | @Mixin(value = LoadController.class, remap = false) 31 | public class LoadControllerMixin { 32 | 33 | @Shadow private Loader loader; 34 | 35 | @Inject(method = "distributeStateMessage(Lnet/minecraftforge/fml/common/LoaderState;[Ljava/lang/Object;)V", at = @At("HEAD")) 36 | private void beforeConstructing(LoaderState state, Object[] eventData, CallbackInfo ci) throws Throwable { 37 | if (state == LoaderState.CONSTRUCTING) { // This state is where Forge adds mod files to ModClassLoader 38 | 39 | ModClassLoader modClassLoader = (ModClassLoader) eventData[0]; 40 | ASMDataTable asmDataTable = (ASMDataTable) eventData[1]; 41 | 42 | // Add mods into the delegated ModClassLoader 43 | for (ModContainer container : this.loader.getActiveModList()) { 44 | modClassLoader.addFile(container.getSource()); 45 | } 46 | 47 | // Gather ILateMixinLoaders 48 | Set interfaceData = asmDataTable.getAll(ILateMixinLoader.class.getName().replace('.', '/')); 49 | Set lateLoaders = new HashSet<>(); 50 | 51 | // Instantiate all @MixinLoader annotated classes 52 | Set annotatedData = asmDataTable.getAll(MixinLoader.class.getName()); 53 | 54 | if (!annotatedData.isEmpty()) { 55 | for (ASMDataTable.ASMData annotated : annotatedData) { 56 | try { 57 | Class clazz = Class.forName(annotated.getClassName()); 58 | MixinBooterPlugin.logInfo("Loading annotated late loader [%s] for its mixins.", clazz.getName()); 59 | Object instance = clazz.newInstance(); 60 | if (instance instanceof ILateMixinLoader) { 61 | lateLoaders.add((ILateMixinLoader) instance); 62 | } 63 | } catch (Throwable t) { 64 | throw new RuntimeException("Unexpected error.", t); 65 | } 66 | } 67 | } 68 | 69 | // Instantiate all ILateMixinLoader implemented classes 70 | if (!interfaceData.isEmpty()) { 71 | for (ASMDataTable.ASMData itf : interfaceData) { 72 | try { 73 | Class clazz = Class.forName(itf.getClassName().replace('/', '.')); 74 | MixinBooterPlugin.logInfo("Loading late loader [%s] for its mixins.", clazz.getName()); 75 | lateLoaders.add((ILateMixinLoader) clazz.newInstance()); 76 | } catch (Throwable t) { 77 | throw new RuntimeException("Unexpected error.", t); 78 | } 79 | } 80 | 81 | // Gather loaded mods for context 82 | Collection presentMods = this.loader.getActiveModList().stream().map(ModContainer::getModId).collect(Collectors.toSet()); 83 | 84 | for (ILateMixinLoader lateLoader : lateLoaders) { 85 | try { 86 | for (String mixinConfig : lateLoader.getMixinConfigs()) { 87 | Context context = new Context(mixinConfig, presentMods); 88 | if (lateLoader.shouldMixinConfigQueue(context)) { 89 | MixinBooterPlugin.logInfo("Adding [%s] mixin configuration.", mixinConfig); 90 | Mixins.addConfiguration(mixinConfig); 91 | lateLoader.onMixinConfigQueued(context); 92 | } 93 | } 94 | } catch (Throwable t) { 95 | MixinBooterPlugin.logError("Failed to execute late loader [%s].", t, lateLoader.getClass().getName()); 96 | } 97 | } 98 | } 99 | 100 | // Append all unconventional mixin configurations gathered via MixinFixer 101 | Set unconventionalConfigs = MixinFixer.retrieveLateMixinConfigs(); 102 | if (!unconventionalConfigs.isEmpty()) { 103 | MixinBooterPlugin.LOGGER.info("Appending unconventional mixin configurations..."); 104 | for (String unconventionalConfig : unconventionalConfigs) { 105 | MixinBooterPlugin.logInfo("Adding [%s] mixin configuration.", unconventionalConfig); 106 | Mixins.addConfiguration(unconventionalConfig); 107 | } 108 | } 109 | 110 | // Rebuild delegated transformers 111 | Field delegatedTransformersField = MixinServiceLaunchWrapper.class.getDeclaredField("delegatedTransformers"); 112 | delegatedTransformersField.setAccessible(true); 113 | delegatedTransformersField.set(MixinService.getService(), null); 114 | 115 | IMixinProcessor processor = ((IMixinTransformer) Proxy.transformer).getProcessor(); 116 | Method selectMethod = processor.getClass().getDeclaredMethod("select", MixinEnvironment.class); 117 | selectMethod.setAccessible(true); 118 | selectMethod.invoke(processor, MixinEnvironment.getCurrentEnvironment()); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanroomMC/MixinBooter/05fc6c7b4b36a714c90eb4cc2f2364c681da0bc8/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [{ 2 | "modid": "${mod_id}", 3 | "name": "${mod_name}", 4 | "version": "${mod_version}", 5 | "mcversion": "1.12.2", 6 | "description": "A mod that provides the Sponge Mixin library, a standard API for mods to load mixins targeting Minecraft and other mods, and associated useful utilities on 1.8 - 1.12.2.", 7 | "authorList": ["Rongmario"], 8 | "credits": "Thanks to LegacyModdingMC + Fabric for providing the initial mixin fork.", 9 | "url": "https://github.com/CleanroomMC/MixinBooter", 10 | "logoFile": "/icon.png" 11 | }] -------------------------------------------------------------------------------- /src/main/resources/mixin.mixinbooter.init.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "zone.rong.mixinbooter.mixin", 3 | "plugin": "zone.rong.mixinbooter.MixinBooterMixinPlugin", 4 | "required": true, 5 | "refmap": "mixins.mixinbooter.refmap.json", 6 | "target": "@env(DEFAULT)", 7 | "minVersion": "0.8", 8 | "compatibilityLevel": "JAVA_8", 9 | "mixins": [ 10 | "CrashReportMixin", 11 | "LoadControllerMixin" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/relocate/java/org/objectweb/asm/commons/ClassRemapper.java: -------------------------------------------------------------------------------- 1 | /*** 2 | * ASM: a very small and fast Java bytecode manipulation framework 3 | * Copyright (c) 2000-2011 INRIA, France Telecom 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the copyright holders nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 28 | * THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | package org.objectweb.asm.commons; 32 | 33 | import org.objectweb.asm.AnnotationVisitor; 34 | import org.objectweb.asm.ClassVisitor; 35 | import org.objectweb.asm.FieldVisitor; 36 | import org.objectweb.asm.MethodVisitor; 37 | import org.objectweb.asm.Opcodes; 38 | import org.objectweb.asm.TypePath; 39 | 40 | /** 41 | * A {@link ClassVisitor} for type remapping. 42 | * 43 | * @author Eugene Kuleshov 44 | */ 45 | public class ClassRemapper extends ClassVisitor { 46 | 47 | protected final Remapper remapper; 48 | 49 | protected String className; 50 | 51 | public ClassRemapper(final ClassVisitor cv, final Remapper remapper) { 52 | this(Opcodes.ASM5, cv, remapper); 53 | } 54 | 55 | protected ClassRemapper(final int api, final ClassVisitor cv, 56 | final Remapper remapper) { 57 | super(api, cv); 58 | this.remapper = remapper; 59 | } 60 | 61 | @Override 62 | public void visit(int version, int access, String name, String signature, 63 | String superName, String[] interfaces) { 64 | this.className = name; 65 | super.visit(version, access, remapper.mapType(name), remapper 66 | .mapSignature(signature, false), remapper.mapType(superName), 67 | interfaces == null ? null : remapper.mapTypes(interfaces)); 68 | } 69 | 70 | @Override 71 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 72 | AnnotationVisitor av = super.visitAnnotation(remapper.mapDesc(desc), 73 | visible); 74 | return av == null ? null : createAnnotationRemapper(av); 75 | } 76 | 77 | @Override 78 | public AnnotationVisitor visitTypeAnnotation(int typeRef, 79 | TypePath typePath, String desc, boolean visible) { 80 | AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, 81 | remapper.mapDesc(desc), visible); 82 | return av == null ? null : createAnnotationRemapper(av); 83 | } 84 | 85 | @Override 86 | public FieldVisitor visitField(int access, String name, String desc, 87 | String signature, Object value) { 88 | FieldVisitor fv = super.visitField(access, 89 | remapper.mapFieldName(className, name, desc), 90 | remapper.mapDesc(desc), remapper.mapSignature(signature, true), 91 | remapper.mapValue(value)); 92 | return fv == null ? null : createFieldRemapper(fv); 93 | } 94 | 95 | @Override 96 | public MethodVisitor visitMethod(int access, String name, String desc, 97 | String signature, String[] exceptions) { 98 | String newDesc = remapper.mapMethodDesc(desc); 99 | MethodVisitor mv = super.visitMethod(access, remapper.mapMethodName( 100 | className, name, desc), newDesc, remapper.mapSignature( 101 | signature, false), 102 | exceptions == null ? null : remapper.mapTypes(exceptions)); 103 | return mv == null ? null : createMethodRemapper(mv); 104 | } 105 | 106 | @Override 107 | public void visitInnerClass(String name, String outerName, 108 | String innerName, int access) { 109 | // TODO should innerName be changed? 110 | super.visitInnerClass(remapper.mapType(name), outerName == null ? null 111 | : remapper.mapType(outerName), innerName, access); 112 | } 113 | 114 | @Override 115 | public void visitOuterClass(String owner, String name, String desc) { 116 | super.visitOuterClass(remapper.mapType(owner), name == null ? null 117 | : remapper.mapMethodName(owner, name, desc), 118 | desc == null ? null : remapper.mapMethodDesc(desc)); 119 | } 120 | 121 | protected FieldVisitor createFieldRemapper(FieldVisitor fv) { 122 | return new FieldRemapper(fv, remapper); 123 | } 124 | 125 | protected MethodVisitor createMethodRemapper(MethodVisitor mv) { 126 | return new MethodRemapper(mv, remapper); 127 | } 128 | 129 | protected AnnotationVisitor createAnnotationRemapper(AnnotationVisitor av) { 130 | return new AnnotationRemapper(av, remapper); 131 | } 132 | } 133 | 134 | --------------------------------------------------------------------------------