├── .gitignore ├── MixBukkit.png ├── TestMixin ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── dragoncommissions │ │ └── testmixin │ │ ├── TestMixin.java │ │ └── examples │ │ └── EDragonArrowShieldRemover.java │ └── resources │ └── plugin.yml ├── dependency-reduced-pom.xml ├── docs └── Getting Started.md ├── pom.xml ├── readme.md └── src └── main ├── java └── com │ └── dragoncommissions │ └── mixbukkit │ ├── MixBukkit.java │ ├── addons │ └── AutoMapper.java │ ├── agent │ ├── AgentMain.java │ ├── ClassesManager.java │ └── JVMAttacher.java │ ├── api │ ├── MixinPlugin.java │ ├── ObfMap.java │ ├── action │ │ ├── MixinAction.java │ │ └── impl │ │ │ ├── MActionCallSuper.java │ │ │ ├── MActionInsertShellCode.java │ │ │ ├── MActionMethodCallSpoofer.java │ │ │ ├── MActionMethodReplacer.java │ │ │ └── MActionPipeline.java │ ├── locator │ │ ├── HookLocator.java │ │ └── impl │ │ │ ├── HLocatorFieldAccess.java │ │ │ ├── HLocatorFieldRead.java │ │ │ ├── HLocatorFieldWrite.java │ │ │ ├── HLocatorHead.java │ │ │ ├── HLocatorMethodInvoke.java │ │ │ └── HLocatorReturn.java │ └── shellcode │ │ ├── IShellCode.java │ │ ├── LocalVarManager.java │ │ ├── ShellCode.java │ │ ├── ShellCodeInfo.java │ │ └── impl │ │ ├── api │ │ ├── CallbackInfo.java │ │ ├── ShellCodeComment.java │ │ ├── ShellCodePrintMessage.java │ │ ├── ShellCodePrintTopStackType.java │ │ └── ShellCodeReflectionMixinPluginMethodCall.java │ │ └── inner │ │ ├── IShellCodeLoadClassFromPCL.java │ │ ├── IShellCodeMethodInvoke.java │ │ ├── IShellCodeNewArrayAndAddContent.java │ │ ├── IShellCodePushInt.java │ │ └── IShellCodeReflectionMethodInvoke.java │ └── utils │ ├── ASMUtils.java │ ├── CustomPrinter.java │ ├── CustomTextifier.java │ ├── PostPreState.java │ └── io │ └── BukkitErrorOutputStream.java └── resources ├── META-INF └── MANIFEST.MF ├── config.yml └── plugin.yml /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.iml 2 | **/.idea 3 | **/target 4 | **/out/ -------------------------------------------------------------------------------- /MixBukkit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonCommissions/MixBukkit/2f3a678e557704fd90796a43754dceb899db8252/MixBukkit.png -------------------------------------------------------------------------------- /TestMixin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.dragoncommissions 8 | TestMixin 9 | jar 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 8 14 | 8 15 | 16 | 17 | 18 | 19 | 20 | net.md-5 21 | specialsource-maven-plugin 22 | 1.2.2 23 | 24 | 25 | package 26 | 27 | remap 28 | 29 | remap-obf 30 | 31 | org.spigotmc:minecraft-server:1.18.1-R0.1-SNAPSHOT:txt:maps-mojang 32 | true 33 | org.spigotmc:spigot:1.18.1-R0.1-SNAPSHOT:jar:remapped-mojang 34 | true 35 | remapped-obf 36 | 37 | 38 | 39 | package 40 | 41 | remap 42 | 43 | remap-spigot 44 | 45 | ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar 46 | org.spigotmc:minecraft-server:1.18.1-R0.1-SNAPSHOT:csrg:maps-spigot 47 | org.spigotmc:spigot:1.18.1-R0.1-SNAPSHOT:jar:remapped-obf 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | com.dragoncommissions 58 | MixBukkit 59 | 1.0-SNAPSHOT 60 | provided 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.18.22 66 | provided 67 | 68 | 69 | org.spigotmc 70 | spigot 71 | 1.18.1-R0.1-SNAPSHOT 72 | remapped-mojang 73 | provided 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /TestMixin/src/main/java/com/dragoncommissions/testmixin/TestMixin.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.testmixin; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.dragoncommissions.mixbukkit.addons.AutoMapper; 5 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 6 | import lombok.SneakyThrows; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | public class TestMixin extends JavaPlugin implements Listener { 11 | 12 | 13 | @Override 14 | @SneakyThrows 15 | public void onEnable() { 16 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream()); 17 | 18 | // EDragonArrowShieldRemover.register(plugin); 19 | 20 | } 21 | 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /TestMixin/src/main/java/com/dragoncommissions/testmixin/examples/EDragonArrowShieldRemover.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.testmixin.examples; 2 | 3 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 4 | import com.dragoncommissions.mixbukkit.api.action.impl.MActionInsertShellCode; 5 | import com.dragoncommissions.mixbukkit.api.locator.impl.HLocatorHead; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.api.CallbackInfo; 7 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.api.ShellCodeReflectionMixinPluginMethodCall; 8 | import lombok.SneakyThrows; 9 | import net.minecraft.world.damagesource.DamageSource; 10 | import net.minecraft.world.entity.boss.enderdragon.phases.AbstractDragonSittingPhase; 11 | import net.minecraft.world.entity.projectile.AbstractArrow; 12 | import org.bukkit.Bukkit; 13 | 14 | public class EDragonArrowShieldRemover { 15 | 16 | @SneakyThrows 17 | public static void register(MixinPlugin plugin) { 18 | plugin.registerMixin("dragon_arrow_blocker_remove", 19 | new MActionInsertShellCode( 20 | new ShellCodeReflectionMixinPluginMethodCall(EDragonArrowShieldRemover.class.getDeclaredMethod("onHurt", AbstractDragonSittingPhase.class, DamageSource.class, float.class, CallbackInfo.class)), 21 | new HLocatorHead() 22 | ), AbstractDragonSittingPhase.class, "onHurt", float.class, DamageSource.class, float.class); 23 | } 24 | 25 | /* 26 | Original Sourcecode of AbstractDragonSittingPhase#onHurt(DamageSource, float): 27 | public float onHurt(DamageSource var0, float var1) { 28 | if (var0.getDirectEntity() instanceof AbstractArrow) { // A 29 | var0.getDirectEntity().setSecondsOnFire(1); // B 30 | return 0.0F; // C 31 | } else { 32 | return super.onHurt(var0, var1); 33 | } 34 | } 35 | 36 | 37 | this code prevents players from attacking dragons with arrows while dragon is in sitting phase. 38 | There's no way to make it possible with vanilla bukkit API other than tracing down EnderDragon#hurt(EnderDragonPart, DamageSource, float) 39 | Well, that's not a good idea. 40 | 41 | In this case, Mixin is the best solution (if you are fine making it only works with 1 version) 42 | 43 | In this mixin, it modifies the original onHurt method, makes a call to EDragonArrowShieldRemover#onHurt(AbstractDragonSittingPhase, DamageSource, float, CallbackInfo) 44 | It checks of the damage source is arrow, if it is then it will return the original damage instead of 0 (0 is returned in vanilla if source is arrow) 45 | Since the "A" will never be called, the arrow will never bounce off again. 46 | */ 47 | 48 | public static void onHurt(AbstractDragonSittingPhase phase, DamageSource source, float damage, CallbackInfo info) { 49 | if (source.getDirectEntity() instanceof AbstractArrow) { 50 | info.setReturned(true); 51 | info.setReturnValue(damage); 52 | Bukkit.broadcastMessage("Successfully hit ender dragon with arrow"); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /TestMixin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | main: com.dragoncommissions.testmixin.TestMixin 2 | name: TestMixin 3 | version: 1.0 4 | -------------------------------------------------------------------------------- /dependency-reduced-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.dragoncommissions 5 | MixBukkit 6 | 1.0-SNAPSHOT 7 | 8 | 9 | 10 | maven-shade-plugin 11 | 3.2.4 12 | 13 | 14 | package 15 | 16 | shade 17 | 18 | 19 | 20 | 21 | *:* 22 | 23 | META-INF/*.SF 24 | META-INF/*.DSA 25 | META-INF/*.RSA 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | spigot-repo 38 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 39 | 40 | 41 | 42 | 43 | org.spigotmc 44 | spigot-api 45 | 1.8.8-R0.1-SNAPSHOT 46 | provided 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | 1.18.22 52 | provided 53 | 54 | 55 | 56 | 8 57 | 8 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/Getting Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | In this tutorial, I'll be teaching you how to start coding your first mixin plugin! 3 | ## Out of date 4 | This document is outdated. Require an update. 5 | 6 | ## Requirements 7 | 1. Some basic Java knowledge 8 | 2. Some basic Reflection knowledge 9 | 3. Understand that doing hacky things like this won't make your plugin cross version support, unless you do things other than things I mentioned in this tutorial. 10 | 11 | ## Add Dependency 12 | We haven't published an artifact to any repository yet, but you can install it to your 13 | local maven repository or add it as a system scoped library. 14 | ### Method 1: Build & Install it to your maven repository 15 | 1. Clone this repository: 16 | ```shell 17 | $ git clone https://github.com/DragonCommissions/MixBukkit 18 | ``` 19 | 2. Build MixBukkit project: 20 | ```shell 21 | $ mvn clean package install 22 | ``` 23 | > Note: Install maven `mvn` command doesn't work 24 | 3. Add MixBukkit to your project dependencies: 25 | ```xml 26 | 27 | com.dragoncommissions 28 | MixBukkit 29 | 1.0-SNAPSHOT 30 | provided 31 | 32 | ``` 33 | 34 | ### Method 2: Install it from command line 35 | ```shell 36 | $ mvn install:install-file -Dfile= -DgroupId=com.dragoncommissions -DartifactId=MixBukkit -Dversion=1.0-SNAPSHOT -Dpackaging=jar 37 | ``` 38 | ```xml 39 | 40 | com.dragoncommissions 41 | MixBukkit 42 | 1.0-SNAPSHOT 43 | provided 44 | 45 | ``` 46 | 47 | ### Method 3: Add the plugin jar as library 48 | ```xml 49 | 50 | com.dragoncommissions 51 | MixBukkit 52 | 1.0-SNAPSHOT 53 | system 54 | ${basedir}/"relative path to MixBukkit.jar" 55 | 56 | ``` 57 | 58 | > Note: You don't need to shade MixBukkit, shading it won't work. 59 | 60 | ## Create your first mixin 61 | You don't need any extra file (as resources) for MixBukkit, which means 62 | you can finish all of them in Java. 63 | Also, If you want a full example, you can check out `TestMixin/`, that's the project 64 | where we test features of MixBukkit. And I'll be using TestMixin as example here. 65 |
66 | There are a few major steps: 67 | 1. Get the `MixinPlugin` instance 68 | 1. Obtain Members Mappings (Optional) 69 | 2. Add your own mixin 70 | 71 | ### Get the `MixinPlugin` instance 72 | To do this, you need to obtain a members mappings first. 73 | Luckily, we have a full auto mapper loader that detects version and remap status. 74 | This also means if you are running a remapped version of Spigot, it won't break, 75 | but I won't be talking about this in this tutorial. 76 |
77 | Mapping in MixBukkit is used to improve your coding experience. Since methods of inner minecraft 78 | (also known as NMS, but those classes have their package name now, so I'll call it 79 | inner minecraft) are obfuscated, it's not easy to figure out what are those, so 80 | people usually use remapped spigot as dependency instead of unmapped. And 81 | when you are using remapped version of it (Check [Spigot's Official Post](https://www.spigotmc.org/threads/spigot-bungeecord-1-17-1-17-1.510208/#post-4184317)), you'll see deobfuscated names, but they 82 | are not their real name, which means if you use those name to find methods with 83 | reflection, it won't find it successfully. With the auto mapper we are having, it will 84 | map the field name string input (let's say: `tickBlockEntities`) into obfuscated name 85 | (`tickBlockEntities` was `R`, so it will be looking for `R` instead of a not existing method: 86 | `tickBlockEntities`) 87 |
88 | Here's how you do it with auto mapping downloader: 89 | ```java 90 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream()); 91 | ``` 92 | Here's how you do it with your own mapping: 93 | ```java 94 | MixinPlugin plugin = MixBukkit.registerMixinPlugin(this, TestMixin.class.getClassLoader().getResourceAsStream("mappings.csrg")); 95 | ``` 96 | > Note: Don't forget to replace TestMixin to your main class name, and mappings.csrg to the location of the mapping 97 | 98 | ### Add your own Mixin 99 | #### Keywords you need to know first 100 | ##### ShellCode 101 | Also be known as Bytecode Generator. For example: `ShellCodeReflectionPluginMethodCall` uses reflection to call plugin methods. Every shellcode should get an annotation: `ShellCodeInfo`, which contains information about the shellcode. While implementing your own the shellcode, you should always annotate it with @ShellCodeInfo with required information. 102 | To get a list of ShellCodes, simply type `ShellCode` in your IDE and let it auto completes: 103 | ![](https://storage.gato.host/61068f9c11c02e002297ebf2/iwGtPu8wD.png) 104 | 105 | ##### MixinAction (MAction) 106 | MixinAction has ability to modify entire method, 107 | which is the lowest level of mixin. If you want to do something special 108 | other than inserting shellcode (for example: replace it with a super call, trash the method), 109 | you can use this. Same as shellcode, you can do get a list of MixinAction with `MAction` and let the IDE list them for you. 110 | Here's an example MixinAction (MActionMethodTrasher), which trashes entire method and replace them with nothing. Note that it doesn't work with variables requires a return value: 111 | ![](https://storage.gato.host/61068f9c11c02e002297ebf2/ov_KRsORz.png) 112 | 113 | ##### HookLocator (HLocator) 114 | HookLocator will return a list of instruction index. 115 | Let's say you want to inject a shellcode into the top of the method, 116 | you would want to use `new MActionInsertShellCode(shellCode, new HLocatorTop())` 117 | 118 |
119 | 120 | #### Example 121 | After knowing some keywords, you can start adding your own mixins. 122 | Let's use `ShellCodeReflectionMixinPluginMethodCall` as example here, since it will 123 | be the most used shellcode.
124 | Here's an example: 125 | ```java 126 | plugin.registerMixin( 127 | "Test Mixin", // Namespace of the mixin, used to identify them/avoid imjecting same mixin multiple times, so any char is allowed 128 | new MActionInsertShellCode( 129 | new ShellCodeReflectionMixinPluginMethodCall(TestMixin.class.getDeclaredMethod("hurt", EnderMan.class, DamageSource.class, float.class, CallbackInfo.class), false), 130 | // If you want a document of ShellCodeReflectionMixinPluginMethodCall, check the docs for that (obviously not Getting Started.md) 131 | new HLocatorTop() 132 | // Inject to top of the method 133 | ), 134 | EnderMan.class, // Target class 135 | "hurt", // Deobfuscated Method Name 136 | boolean.class, // Return Type 137 | DamageSource.class, float.class // Parameter Types 138 | ); 139 | ``` 140 | 141 | As you can see, it'll be looking for `TestMixin.hurt()` (Line 4), here's the hurt() example: 142 | ```java 143 | public static void hurt(EnderMan test, DamageSource damageSource, float damage, CallbackInfo callBackInfo) { 144 | Bukkit.broadcastMessage(test.getDisplayName().getString() + " gets hurt from " + damageSource.getMsgId() + " (Damage amount: " + damage + ")"); 145 | callBackInfo.setReturned(true); // If it's true, it will return something 146 | callBackInfo.setReturnValue(false); // Return value of it. hurt() in vanilla returns a boolean, so I returned boolean 147 | } 148 | ``` 149 | 150 | ## Limitation 151 | Since we don't want to make the framework/library/api very heavy, we decided to 152 | self-attach & redefine classes (like hotswap) instead of transform every class. 153 | If you have ever used HotSwap, you know the limitation. No new method, no new field 154 | , no structure change, and no new class define. I think all you need to beware of is 155 | no new method for overriding. Here's an example: 156 | 157 | ```java 158 | public class LivingEntity { 159 | public void hurt(DamageSource source, float damage) { 160 | // Blablabla 161 | } 162 | } 163 | 164 | public class Skeleton extends LivingEntity { 165 | 166 | } 167 | ``` 168 | In this case, `hurt` is not defined in Skeleton class, but LivingEntity class, you are not allowed 169 | to create mixin in `Skeleton.hurt`, so technically you can't hook into `Skeleton.hurt()`. 170 | This is a pretty bad thing, but if we think outside the box: 171 | ```java 172 | public static void hurt(LivingEntity entity, DamageSource damageSource, float damage, CallbackInfo callBackInfo) { 173 | if (entity instanceof Skeleton) { 174 | Bukkit.broadcastMessage(entity.getDisplayName().getString() + " gets hurt from " + damageSource.getMsgId() + " (Damage amount: " + damage + ")"); 175 | callBackInfo.setReturned(true); 176 | callBackInfo.setReturnValue(false); 177 | } 178 | } 179 | ``` 180 | Yes, you can just check the class and see if it's an instance of Skeleton. 181 | It's same as hooking into `Skeleton.hurt()` 182 | 183 | ## Mapping API 184 | While using `HLocatorMethodInvoke`, it requires a java reflection api `Method` object, 185 | which can be obtained with `getDeclaredMethod`, but obviously it won't work 186 | since the method name on the server is obfuscated. We provided an API for this, 187 | you can simply call `plugin.getMethod()` to solve this problem, it takes 188 | basically same parameter as getDeclaredMethod, but it will find the obfuscated version 189 | according to the members map loaded.
190 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.dragoncommissions 8 | MixBukkit 9 | jar 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 8 14 | 8 15 | 16 | 17 | 18 | 19 | spigot-repo 20 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 21 | 22 | 23 | 24 | 25 | 26 | org.spigotmc 27 | spigot-api 28 | 1.8.8-R0.1-SNAPSHOT 29 | provided 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.22 35 | provided 36 | 37 | 38 | org.ow2.asm 39 | asm 40 | 9.2 41 | compile 42 | 43 | 44 | org.ow2.asm 45 | asm-util 46 | 9.2 47 | compile 48 | 49 | 50 | com.github.olivergondza 51 | maven-jdk-tools-wrapper 52 | 0.1 53 | compile 54 | 55 | 56 | org.javassist 57 | javassist 58 | 3.28.0-GA 59 | compile 60 | 61 | 62 | net.bytebuddy 63 | byte-buddy-agent 64 | 1.12.8 65 | compile 66 | 67 | 68 | io.github.kasukusakura 69 | jvm-self-attach 70 | 0.0.1 71 | compile 72 | 73 | 74 | io.github.karlatemp 75 | unsafe-accessor 76 | 1.6.1 77 | compile 78 | 79 | 80 | org.eclipse.jgit 81 | org.eclipse.jgit 82 | 5.13.0.202109080827-r 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-shade-plugin 91 | 3.2.4 92 | 93 | 94 | package 95 | shade 96 | 97 | 98 | 99 | *:* 100 | 101 | META-INF/*.SF 102 | META-INF/*.DSA 103 | META-INF/*.RSA 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![MixBukkit](https://github.com/DragonCommissions/MixBukkit/raw/master/MixBukkit.png) 2 | MixBukkit is a mixin framework inspired by SpongePowered's Mixin framework using ASM. 3 | 4 | ## When will you need it 5 | Let's say you want to hook something in `Skeleton.class`, but obviously you can't just edit spigot source code. Having a custom build just for a plugin is ridiculous. MixBukkit provides an easy to use API that allows you to hook anything in NMS/CraftBukkit/Spigot API/Plugins and even libraries. 6 | 7 | ## Environment 8 | In theory, it should work from Java 8 ~ Java latest, Linux & Windows & MacOS, but I only tested it on Linux with Java 17.
9 | Minecraft version is not limited, but it will result in mapping different 10 | 11 | ## Basic Usage 12 | (Please check `docs/Getting Started.md` for more information) 13 | ### Mapping 14 | #### Method 1. Use Spigot's Members Mapping 15 | If you want to make things easy/fast/good/great/simple, you can use this method. 16 | Simply skip to next step, you don't need to worry about mapping 17 | #### Method 2. Use custom mapping 18 | Since we get an AutoMapper, you can use it instead, but you can also do this if 19 | you have a custom mapping to load. Doing the following thing will get you a 20 | working vanilla mapping. You can also grab it from `%server_root%/mappings.csrg`. 21 | 1. Run buildtool with this following command: `java -jar BuildTool.jar --rev --remapped` 22 | 2. After that, go to your local maven repository (usually `{user.home}/.m2/repository`), and copy `minecraft-server--maps-spigot-members.csrg`. For example, it's in `/home/fan87/.m2/repository/org/spigotmc/minecraft-server/1.18.1-R0.1-SNAPSHOT/` on my computer 23 | 3. Paste that file into the same directory as `plugin.yml`, and name it to anything you want. For example: `mapping.csrg` 24 | #### Bad way of loading a custom members mapping 25 | Do not replace `%server_root%/mappings.csrg` to load a custom mapping. It 26 | will probably kill all plugins that is using Mixin.
27 | Unless you want to make the mapping globally (let's say you are the user, you can do this). 28 | 29 | ### Register `MixinPlugin` 30 | If you wish to use Spigot's members mapping: 31 | ```java 32 | // onEnable() 33 | MixinPlugin mixinPlugin = MixBukkit.registerMixinPlugin(this, AutoMapper.getMappingAsStream()); 34 | ``` 35 | If you wish to use a custom members mapping: 36 | ```java 37 | // onEnable() 38 | MixinPlugin mixinPlugin = MixBukkit.registerMixinPlugin(this, TestMixin.class.getClassLoader().getResourceAsStream("mapping.csrg" /* Type the mapping location here */)); 39 | ``` 40 | After registering MixinPlugin, you can start registering mixins. 41 | Please check `TestMixin/` module for examples! 42 | 43 | ### Project Keywords/Tips and tricks 44 | #### ShellCode 45 | Also be known as Bytecode Generator. For example: `ShellCodeReflectionPluginMethodCall` uses reflection to call plugin methods. Every shellcode should get an annotation: `ShellCodeInfo`, which contains information about the shellcode. While implementing your own the shellcode, you should always annotate it with @ShellCodeInfo with required information. 46 | To get a list of ShellCodes, simply type `ShellCode` in your IDE and let it auto completes: 47 | ![](https://storage.gato.host/61068f9c11c02e002297ebf2/iwGtPu8wD.png) 48 | 49 | #### MixinAction (MAction) 50 | MixinAction has ability to modify entire method, 51 | which is the lowest level of mixin. If you want to do something special 52 | other than inserting shellcode (for example: replace it with a super call, trash the method), 53 | you can use this. Same as shellcode, you can do get a list of MixinAction with `MAction` and let the IDE list them for you. 54 | Here's an example MixinAction (MActionMethodTrasher), which trashes entire method and replace them with nothing. Note that it doesn't work with variables requires a return value: 55 | ![](https://storage.gato.host/61068f9c11c02e002297ebf2/ov_KRsORz.png) 56 | 57 | #### HookLocator (HLocator) 58 | HookLocator will return a list of instruction index. 59 | Let's say you want to inject a shellcode into the top of the method, 60 | you would want to use `new MActionInsertShellCode(shellCode, new HLocatorTop())` 61 | 62 | ## Advanced Usage 63 | Coming Soon! (JavaDoc also coming soon : D ) 64 | 65 | ## Gallery 66 | ![](https://storage.gato.host/61068f9c11c02e002297ebf2/ZPMCC0j-t.png) 67 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/MixBukkit.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit; 2 | 3 | import com.dragoncommissions.mixbukkit.addons.AutoMapper; 4 | import com.dragoncommissions.mixbukkit.agent.ClassesManager; 5 | import com.dragoncommissions.mixbukkit.agent.JVMAttacher; 6 | import com.dragoncommissions.mixbukkit.api.ObfMap; 7 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 8 | import com.dragoncommissions.mixbukkit.utils.io.BukkitErrorOutputStream; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | import lombok.SneakyThrows; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.ChatColor; 14 | import org.bukkit.configuration.file.YamlConfiguration; 15 | import org.bukkit.plugin.Plugin; 16 | import org.bukkit.plugin.java.JavaPlugin; 17 | 18 | import java.io.File; 19 | import java.io.InputStream; 20 | import java.lang.instrument.Instrumentation; 21 | import java.lang.reflect.Method; 22 | import java.net.URLClassLoader; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.jar.JarFile; 26 | 27 | public class MixBukkit extends JavaPlugin { 28 | 29 | public final static String VERSION = "0.1"; 30 | public final static BuildType BUILD_TYPE = BuildType.SNAPSHOT; 31 | public static boolean DEBUG = BUILD_TYPE.isDevBuild(); 32 | public static boolean WRITE_TRANSFORMED_CLASS = false; 33 | public static boolean SAFE_MODE = true; 34 | public static Instrumentation INSTRUMENTATION = null; 35 | public static boolean PREPARED = false; 36 | public static BukkitErrorOutputStream ERROR_OUTPUT_STREAM = new BukkitErrorOutputStream(); 37 | 38 | @Getter 39 | private File pluginFile; 40 | 41 | @Getter 42 | private static JVMAttacher jvmAttacher; 43 | 44 | public static ClassesManager classesManager; 45 | 46 | 47 | @Getter 48 | private static final Map plugins = new HashMap<>(); 49 | 50 | @Override 51 | public void onDisable() { 52 | 53 | } 54 | 55 | @Override 56 | @SneakyThrows 57 | public void onEnable() { 58 | URLClassLoader parent = ((URLClassLoader) getClassLoader().getParent()); 59 | pluginFile = getFile(); 60 | 61 | loadConfig(); 62 | 63 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "=-=-=-=-= MixBukkit Loader =-=-=-=-="); 64 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Version: " + VERSION); 65 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Build Type: " + BUILD_TYPE); 66 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "MC Version: " + AutoMapper.getMCVersion()); 67 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "Server Remapped: " + !AutoMapper.isObfuscatedBuild()); 68 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + ""); 69 | if (!SAFE_MODE) { 70 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Warning: Safe mode is disabled! It might load invalid class and crash the Server/JVM"); 71 | } 72 | if (!DEBUG) { 73 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// If you wish to see debug messages, please enable \"debug-mode\" in your config file"); 74 | } else { 75 | if (!WRITE_TRANSFORMED_CLASS) { 76 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// If you wish to see transformed version of class (for testing purposes), you can enable \"write-transformed-class\" in config!"); 77 | } 78 | } 79 | if (WRITE_TRANSFORMED_CLASS) { 80 | getServer().getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Write output class enabled! Transformed classes will be renamed and go into your temp folder."); 81 | } 82 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + ""); 83 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "~~ Started loading ~~"); 84 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + " - Attaching to JVM..."); 85 | jvmAttacher = new JVMAttacher(this); 86 | jvmAttacher.attach(); 87 | if (INSTRUMENTATION == null) { 88 | setEnabled(false); 89 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "- Failed grabbing instrumentation! If you believe this is an issue, please open a ticket"); 90 | getServer().getConsoleSender().sendMessage(ChatColor.RED + ""); 91 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "======= FAILED GETTING INSTRUMENTATION ======"); 92 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "Please check those things before opening an issue:"); 93 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "1. Do you have -XX:+DisableAttachMechanism? If yes, remove it from server start command."); 94 | getServer().getConsoleSender().sendMessage(ChatColor.RED + "2. Does the server have permission to spawn a process? If no, give it. Normally yes unless you are using server panel that limits the privilege"); 95 | getServer().getConsoleSender().sendMessage(ChatColor.RED + ""); 96 | throw new NullPointerException("Instrumentation is null"); 97 | } 98 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "- Finished Attaching!"); 99 | getServer().getConsoleSender().sendMessage(ChatColor.YELLOW + "- Preparing class transformers..."); 100 | ClassesManager.init(); 101 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "- Finished preparing class transformers!"); 102 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + ""); 103 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + ""); 104 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Finished loading MixBukkit!"); 105 | getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); 106 | PREPARED = true; 107 | } 108 | 109 | private void loadConfig() { 110 | try { 111 | 112 | YamlConfiguration config = new YamlConfiguration(); 113 | File configFile = new File(getDataFolder(), "config.yml"); 114 | if (!getDataFolder().exists()) getDataFolder().mkdirs(); 115 | if (!configFile.exists()) configFile.createNewFile(); 116 | 117 | config.load(configFile); 118 | if (!config.contains("safe-mode")) config.set("safe-mode", SAFE_MODE); 119 | if (!config.contains("debug-mode")) config.set("debug-mode", DEBUG); 120 | if (!config.contains("write-transformed-class")) config.set("write-transformed-class", WRITE_TRANSFORMED_CLASS); 121 | SAFE_MODE = config.getBoolean("safe-mode"); 122 | DEBUG = config.getBoolean("debug-mode"); 123 | WRITE_TRANSFORMED_CLASS = config.getBoolean("write-transformed-class"); 124 | config.save(configFile); 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | 130 | public static MixinPlugin registerMixinPlugin(Plugin plugin, InputStream membersMapStream) { 131 | MixinPlugin mixinPlugin = plugins.get(plugin.getName()); 132 | if (mixinPlugin != null) { 133 | return mixinPlugin; 134 | } 135 | mixinPlugin = new MixinPlugin(plugin, new ObfMap(membersMapStream)); 136 | plugins.put(plugin.getName(), mixinPlugin); 137 | try { 138 | Method getFile = JavaPlugin.class.getDeclaredMethod("getFile"); 139 | getFile.setAccessible(true); 140 | File pluginFile = ((File) getFile.invoke(plugin)); 141 | pluginFile = pluginFile.getAbsoluteFile(); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | } 145 | 146 | 147 | return mixinPlugin; 148 | } 149 | 150 | @SneakyThrows 151 | public static void addLibrary(File file) { 152 | INSTRUMENTATION.appendToSystemClassLoaderSearch(new JarFile(file)); 153 | 154 | if (DEBUG) { 155 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Loading " + file.getAbsolutePath()); 156 | } 157 | } 158 | 159 | @AllArgsConstructor 160 | @Getter 161 | public enum BuildType { 162 | SNAPSHOT(true), 163 | BETA(false), 164 | RELEASE(false); 165 | 166 | boolean devBuild; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/addons/AutoMapper.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.addons; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.google.common.collect.BiMap; 5 | import com.google.common.collect.HashBiMap; 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonObject; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.SneakyThrows; 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.ChatColor; 13 | import org.eclipse.jgit.api.Git; 14 | 15 | import java.io.*; 16 | import java.net.URL; 17 | import java.net.URLConnection; 18 | import java.nio.file.Files; 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | public class AutoMapper { 27 | 28 | private static boolean prepared = false; 29 | private static File mappingFile; 30 | 31 | @SneakyThrows 32 | public static InputStream getMappingAsStream() { 33 | if (!prepared) { 34 | try { 35 | prepareMapping(); 36 | } catch (Exception e) { 37 | if (MixBukkit.DEBUG) { 38 | e.printStackTrace(); 39 | } 40 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Error loading mapping! Have you connected to the internet?"); 41 | if (MixBukkit.SAFE_MODE) { 42 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Server shutdown because safe mode is on, not loading mapping correctly may cause critical bugs/saves corruption."); 43 | Bukkit.getServer().shutdown(); 44 | throw e; 45 | } 46 | } 47 | prepared = true; 48 | } 49 | if (mappingFile == null) return null; // Don't load any mapping 50 | return new FileInputStream(mappingFile); 51 | } 52 | @SneakyThrows 53 | private static void prepareMapping() { 54 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Detecting mapping info..."); 55 | if (!shouldLoadMapping()) { 56 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] You don't need any mapping for this build!"); 57 | return; 58 | } 59 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Mappings required! Preparing mapping..."); 60 | mappingFile = new File("mappings.csrg"); 61 | if (mappingFile.exists()) { 62 | if (!mappingFile.isDirectory()) { 63 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Pre-downloaded mapping detected! Using it. If anything went wrong, please try deleting " + ChatColor.DARK_GRAY + mappingFile.getAbsolutePath() + ChatColor.GREEN + " and try again"); 64 | return; 65 | } 66 | mappingFile.delete(); 67 | } 68 | File buildDataDir = new File(System.getProperty("user.home"), "BuildData"); 69 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Fetching BuildData version from Spigot API..."); 70 | Gson gson = new Gson(); 71 | URLConnection connection = new URL("https://hub.spigotmc.org/versions/" + getMCVersion() + ".json").openConnection(); 72 | JsonObject object = gson.fromJson(new InputStreamReader(connection.getInputStream()), JsonObject.class); 73 | String buildDataVersion = object.get("refs").getAsJsonObject().get("BuildData").getAsString(); 74 | Git buildData = null; 75 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Fetched BuildData Version: " + buildDataVersion + "!"); 76 | if (buildDataDir.exists()) { 77 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Found Spigot's BuildData cache at " + buildDataDir.getAbsolutePath() + "! Doing some simple verification..."); 78 | try { 79 | buildData = Git.open(buildDataDir); 80 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Verified! Updating BuildData..."); 81 | buildData.pull().call(); 82 | } catch (Exception e) { 83 | buildDataDir.delete(); 84 | } 85 | } 86 | if (!buildDataDir.exists()) { 87 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Cloning Spigot's BuildData repository to " + buildDataDir.getAbsolutePath() + " . It should take a while (Usually around 35 MB), but it's a one time process (across every server)"); 88 | buildData = Git.cloneRepository().setURI("https://hub.spigotmc.org/stash/scm/spigot/builddata.git").setDirectory(buildDataDir).call(); 89 | } 90 | 91 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Successfully fetched BuildData! Switching to " + buildDataVersion); 92 | buildData.checkout().setName(buildDataVersion).call(); 93 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Checking version info..."); 94 | VersionInfo versionInfo = gson.fromJson(new FileReader(new File(buildDataDir, "info.json")), VersionInfo.class); 95 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Scanning for members mapping..."); 96 | File classMappings = new File(buildDataDir, "mappings/" + versionInfo.classMappings); 97 | if (versionInfo.memberMappings == null) { 98 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Didn't find a members mapping! Building one..."); 99 | MapUtil mapUtil = new MapUtil(); 100 | mapUtil.loadBuk(classMappings); 101 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Downloading Minecraft's Mappings & Building Members Mappings..."); 102 | InputStream inputStream = new URL(versionInfo.mappingsUrl).openConnection().getInputStream(); 103 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 104 | while (true) { 105 | int read = inputStream.read(); 106 | if (read == -1) break; 107 | outputStream.write(read); 108 | } 109 | mapUtil.makeFieldMaps(new String(outputStream.toByteArray()), mappingFile, true); 110 | } else { 111 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[!] Found a pre-built members mapping! Extracting..."); 112 | mappingFile.createNewFile(); 113 | Files.copy(new File(buildDataDir, "mappings/" + versionInfo.memberMappings).toPath(), mappingFile.toPath()); 114 | } 115 | Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[!] Finished loading mappings!"); 116 | 117 | } 118 | 119 | public static String getMCVersion() { 120 | return Bukkit.getBukkitVersion().split("-")[0]; 121 | } 122 | 123 | private static boolean shouldLoadMapping() { 124 | return Integer.parseInt(getMCVersion().split("\\.")[1]) >= 17 && isObfuscatedBuild(); 125 | // Remapped option is only available after 1.17 126 | } 127 | 128 | public static boolean isObfuscatedBuild() { 129 | try { 130 | Class aClass = Class.forName("net.minecraft.world.entity.EntityLiving"); 131 | return true; 132 | } catch (Throwable ignored) {} 133 | return false; 134 | } 135 | 136 | /** 137 | * Source: Spigot BuildTools 138 | */ 139 | @Data 140 | @AllArgsConstructor 141 | private static class VersionInfo 142 | { 143 | 144 | private String minecraftVersion; 145 | private String accessTransforms; 146 | private String classMappings; 147 | private String memberMappings; 148 | private String packageMappings; 149 | private String minecraftHash; 150 | private String classMapCommand; 151 | private String memberMapCommand; 152 | private String finalMapCommand; 153 | private String decompileCommand; 154 | private String serverUrl; 155 | private String mappingsUrl; 156 | private String spigotVersion; 157 | private int toolsVersion = -1; 158 | 159 | public VersionInfo(String minecraftVersion, String accessTransforms, String classMappings, String memberMappings, String packageMappings, String minecraftHash) 160 | { 161 | this.minecraftVersion = minecraftVersion; 162 | this.accessTransforms = accessTransforms; 163 | this.classMappings = classMappings; 164 | this.memberMappings = memberMappings; 165 | this.packageMappings = packageMappings; 166 | this.minecraftHash = minecraftHash; 167 | } 168 | 169 | public VersionInfo(String minecraftVersion, String accessTransforms, String classMappings, String memberMappings, String packageMappings, String minecraftHash, String decompileCommand) 170 | { 171 | this.minecraftVersion = minecraftVersion; 172 | this.accessTransforms = accessTransforms; 173 | this.classMappings = classMappings; 174 | this.memberMappings = memberMappings; 175 | this.packageMappings = packageMappings; 176 | this.minecraftHash = minecraftHash; 177 | this.decompileCommand = decompileCommand; 178 | } 179 | 180 | public String getShaServerHash() 181 | { 182 | return hashFromUrl( serverUrl ); 183 | } 184 | 185 | public String getShaMappingsHash() 186 | { 187 | return hashFromUrl( mappingsUrl ); 188 | } 189 | private static final Pattern URL_PATTERN = Pattern.compile( "https://launcher.mojang.com/v1/objects/([0-9a-f]{40})/.*" ); 190 | 191 | public static String hashFromUrl(String url) 192 | { 193 | if ( url == null ) 194 | { 195 | return null; 196 | } 197 | 198 | Matcher match = URL_PATTERN.matcher( url ); 199 | return ( match.find() ) ? match.group( 1 ) : null; 200 | } 201 | } 202 | private static class MapUtil 203 | { 204 | 205 | private static final Pattern MEMBER_PATTERN = Pattern.compile( "(?:\\d+:\\d+:)?(.*?) (.*?) \\-> (.*)" ); 206 | // 207 | private List header = new ArrayList<>(); 208 | private final BiMap obf2Buk = HashBiMap.create(); 209 | private final BiMap moj2Obf = HashBiMap.create(); 210 | 211 | public void loadBuk(File bukClasses) throws IOException 212 | { 213 | for ( String line : Files.readAllLines( bukClasses.toPath() ) ) 214 | { 215 | if ( line.startsWith( "#" ) ) 216 | { 217 | header.add( line ); 218 | continue; 219 | } 220 | 221 | String[] split = line.split( " " ); 222 | if ( split.length == 2 ) 223 | { 224 | obf2Buk.put( split[0], split[1] ); 225 | } 226 | } 227 | } 228 | 229 | public void makeFieldMaps(String mojIn, File fields, boolean includeMethods) throws IOException 230 | { 231 | List lines = new ArrayList<>(); 232 | if ( includeMethods ) 233 | { 234 | for (String line : mojIn.split("\n")) { 235 | lines.add(line); 236 | if ( line.startsWith( "#" ) ) 237 | { 238 | continue; 239 | } 240 | 241 | if ( line.endsWith( ":" ) ) 242 | { 243 | String[] parts = line.split( " -> " ); 244 | String orig = parts[0].replace( '.', '/' ); 245 | String obf = parts[1].substring( 0, parts[1].length() - 1 ).replace( '.', '/' ); 246 | 247 | moj2Obf.put( orig, obf ); 248 | } 249 | } 250 | } 251 | 252 | List outFields = new ArrayList<>( header ); 253 | 254 | String currentClass = null; 255 | outer: 256 | for ( String line : mojIn.split("\n") ) 257 | { 258 | if ( line.startsWith( "#" ) ) 259 | { 260 | continue; 261 | } 262 | line = line.trim(); 263 | 264 | if ( line.endsWith( ":" ) ) 265 | { 266 | currentClass = null; 267 | 268 | String[] parts = line.split( " -> " ); 269 | String orig = parts[0].replace( '.', '/' ); 270 | String obf = parts[1].substring( 0, parts[1].length() - 1 ).replace( '.', '/' ); 271 | 272 | String buk = deobfClass( obf, obf2Buk ); 273 | if ( buk == null ) 274 | { 275 | continue; 276 | } 277 | 278 | currentClass = buk; 279 | } else if ( currentClass != null ) 280 | { 281 | Matcher matcher = MEMBER_PATTERN.matcher( line ); 282 | matcher.find(); 283 | 284 | String obf = matcher.group( 3 ); 285 | String nameDesc = matcher.group( 2 ); 286 | if ( !nameDesc.contains( "(" ) ) 287 | { 288 | if ( nameDesc.equals( obf ) || nameDesc.contains( "$" ) ) 289 | { 290 | continue; 291 | } 292 | if ( !includeMethods && ( obf.equals( "if" ) || obf.equals( "do" ) ) ) 293 | { 294 | obf += "_"; 295 | } 296 | 297 | outFields.add( currentClass + " " + obf + " " + nameDesc ); 298 | } else if ( includeMethods ) 299 | { 300 | String sig = csrgDesc( moj2Obf, obf2Buk, nameDesc.substring( nameDesc.indexOf( '(' ) ), matcher.group( 1 ) ); 301 | String mojName = nameDesc.substring( 0, nameDesc.indexOf( '(' ) ); 302 | 303 | if ( obf.equals( mojName ) || mojName.contains( "$" ) || obf.equals( "" ) || obf.equals( "" ) ) 304 | { 305 | continue; 306 | } 307 | outFields.add( currentClass + " " + obf + " " + sig + " " + mojName ); 308 | } 309 | } 310 | } 311 | 312 | Collections.sort( outFields ); 313 | fields.createNewFile(); 314 | Files.write( fields.toPath(), outFields ); 315 | } 316 | 317 | public void makeCombinedMaps(File out, File... members) throws IOException 318 | { 319 | List combined = new ArrayList<>( header ); 320 | 321 | for ( Map.Entry map : obf2Buk.entrySet() ) 322 | { 323 | combined.add( map.getKey() + " " + map.getValue() ); 324 | } 325 | 326 | for ( File member : members ) 327 | { 328 | for ( String line : Files.readAllLines( member.toPath() ) ) 329 | { 330 | if ( line.startsWith( "#" ) ) 331 | { 332 | continue; 333 | } 334 | line = line.trim(); 335 | 336 | String[] split = line.split( " " ); 337 | if ( split.length == 3 ) 338 | { 339 | String clazz = split[0]; 340 | String orig = split[1]; 341 | String targ = split[2]; 342 | 343 | combined.add( deobfClass( clazz, obf2Buk.inverse() ) + " " + orig + " " + targ ); 344 | } else if ( split.length == 4 ) 345 | { 346 | String clazz = split[0]; 347 | String orig = split[1]; 348 | String desc = split[2]; 349 | String targ = split[3]; 350 | 351 | combined.add( deobfClass( clazz, obf2Buk.inverse() ) + " " + orig + " " + toObf( desc, obf2Buk.inverse() ) + " " + targ ); 352 | } 353 | } 354 | } 355 | 356 | Files.write( out.toPath(), combined ); 357 | } 358 | 359 | public static String deobfClass(String obf, Map classMaps) 360 | { 361 | String buk = classMaps.get( obf ); 362 | if ( buk == null ) 363 | { 364 | StringBuilder inner = new StringBuilder(); 365 | 366 | while ( buk == null ) 367 | { 368 | int idx = obf.lastIndexOf( '$' ); 369 | if ( idx == -1 ) 370 | { 371 | return null; 372 | } 373 | inner.insert( 0, obf.substring( idx ) ); 374 | obf = obf.substring( 0, idx ); 375 | 376 | buk = classMaps.get( obf ); 377 | } 378 | 379 | buk += inner; 380 | } 381 | return buk; 382 | } 383 | 384 | public static String toObf(String desc, Map map) 385 | { 386 | desc = desc.substring( 1 ); 387 | StringBuilder out = new StringBuilder( "(" ); 388 | if ( desc.charAt( 0 ) == ')' ) 389 | { 390 | desc = desc.substring( 1 ); 391 | out.append( ')' ); 392 | } 393 | while ( desc.length() > 0 ) 394 | { 395 | desc = obfType( desc, map, out ); 396 | if ( desc.length() > 0 && desc.charAt( 0 ) == ')' ) 397 | { 398 | desc = desc.substring( 1 ); 399 | out.append( ')' ); 400 | } 401 | } 402 | return out.toString(); 403 | } 404 | 405 | public static String obfType(String desc, Map map, StringBuilder out) 406 | { 407 | int size = 1; 408 | switch ( desc.charAt( 0 ) ) 409 | { 410 | case 'B': 411 | case 'C': 412 | case 'D': 413 | case 'F': 414 | case 'I': 415 | case 'J': 416 | case 'S': 417 | case 'Z': 418 | case 'V': 419 | out.append( desc.charAt( 0 ) ); 420 | break; 421 | case '[': 422 | out.append( "[" ); 423 | return obfType( desc.substring( 1 ), map, out ); 424 | case 'L': 425 | String type = desc.substring( 1, desc.indexOf( ";" ) ); 426 | size += type.length() + 1; 427 | out.append( "L" ).append( map.containsKey( type ) ? map.get( type ) : type ).append( ";" ); 428 | } 429 | return desc.substring( size ); 430 | } 431 | 432 | private static String csrgDesc(Map first, Map second, String args, String ret) 433 | { 434 | String[] parts = args.substring( 1, args.length() - 1 ).split( "," ); 435 | StringBuilder desc = new StringBuilder( "(" ); 436 | for ( String part : parts ) 437 | { 438 | if ( part.isEmpty() ) 439 | { 440 | continue; 441 | } 442 | desc.append( toJVMType( first, second, part ) ); 443 | } 444 | desc.append( ")" ); 445 | desc.append( toJVMType( first, second, ret ) ); 446 | return desc.toString(); 447 | } 448 | 449 | private static String toJVMType(Map first, Map second, String type) 450 | { 451 | switch ( type ) 452 | { 453 | case "byte": 454 | return "B"; 455 | case "char": 456 | return "C"; 457 | case "double": 458 | return "D"; 459 | case "float": 460 | return "F"; 461 | case "int": 462 | return "I"; 463 | case "long": 464 | return "J"; 465 | case "short": 466 | return "S"; 467 | case "boolean": 468 | return "Z"; 469 | case "void": 470 | return "V"; 471 | default: 472 | if ( type.endsWith( "[]" ) ) 473 | { 474 | return "[" + toJVMType( first, second, type.substring( 0, type.length() - 2 ) ); 475 | } 476 | String clazzType = type.replace( '.', '/' ); 477 | String obf = deobfClass( clazzType, first ); 478 | String mappedType = deobfClass( ( obf != null ) ? obf : clazzType, second ); 479 | 480 | return "L" + ( ( mappedType != null ) ? mappedType : clazzType ) + ";"; 481 | } 482 | } 483 | } 484 | 485 | 486 | } 487 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/agent/AgentMain.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | public class AgentMain { 6 | 7 | public static void agentmain(Instrumentation instrumentation, String args) { 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/agent/ClassesManager.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import javassist.ClassPool; 6 | import javassist.CtClass; 7 | import org.objectweb.asm.tree.ClassNode; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.lang.instrument.ClassFileTransformer; 11 | import java.lang.instrument.IllegalClassFormatException; 12 | import java.lang.instrument.UnmodifiableClassException; 13 | import java.security.ProtectionDomain; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | public class ClassesManager { 18 | 19 | 20 | public static Map classes = new HashMap<>(); 21 | public static Map classNodes = new HashMap<>(); 22 | private static ClassPool cp = ClassPool.getDefault(); 23 | 24 | public static void init() { 25 | MixBukkit.INSTRUMENTATION.addTransformer(new ClassFileTransformer() { 26 | @Override 27 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 28 | classes.put(className.replace("/", "."), classfileBuffer); 29 | return classfileBuffer; 30 | } 31 | }, true); 32 | } 33 | 34 | public static ClassNode getClassNode(String name) { 35 | ClassNode classNode1 = classNodes.get(name); 36 | if (classNode1 == null) { 37 | byte[] classBytecode = getClassBytecode(name); 38 | if (classBytecode == null) return null; 39 | classNode1 = ASMUtils.toClassNode(classBytecode); 40 | return classNode1; 41 | } 42 | return classNode1; 43 | } 44 | 45 | public synchronized static byte[] getClassBytecode(String name) { 46 | name = name.replace("/", "."); 47 | byte[] bytes = classes.get(name); 48 | if (bytes == null) { 49 | Class[] allLoadedClasses = MixBukkit.INSTRUMENTATION.getAllLoadedClasses(); 50 | for (Class allLoadedClass : allLoadedClasses) { 51 | if (allLoadedClass.getName().equals(name)) { 52 | try { 53 | MixBukkit.INSTRUMENTATION.retransformClasses(allLoadedClass); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | } 59 | } 60 | return classes.get(name); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/agent/JVMAttacher.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import io.github.karlatemp.unsafeaccessor.UnsafeAccess; 5 | import io.github.kasukusakura.jsa.JvmSelfAttach; 6 | import lombok.SneakyThrows; 7 | 8 | import java.io.File; 9 | import java.lang.management.ManagementFactory; 10 | 11 | public class JVMAttacher { 12 | 13 | private MixBukkit mixBukkit; 14 | 15 | public JVMAttacher(MixBukkit mixBukkit) { 16 | this.mixBukkit = mixBukkit; 17 | } 18 | 19 | @SneakyThrows 20 | public void attach() { 21 | JvmSelfAttach.init(new File(System.getProperty("java.io.tmpdir"))); 22 | MixBukkit.INSTRUMENTATION = JvmSelfAttach.getInstrumentation(); 23 | 24 | } 25 | 26 | public int getCurrentPID() { 27 | return Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); 28 | } 29 | 30 | static final UnsafeAccess UA = UnsafeAccess.getInstance(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/MixinPlugin.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.dragoncommissions.mixbukkit.agent.ClassesManager; 5 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import com.dragoncommissions.mixbukkit.utils.CustomTextifier; 8 | import lombok.Getter; 9 | import lombok.SneakyThrows; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.ChatColor; 12 | import org.bukkit.plugin.Plugin; 13 | import org.objectweb.asm.*; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.MethodNode; 16 | import org.objectweb.asm.tree.analysis.Analyzer; 17 | import org.objectweb.asm.tree.analysis.AnalyzerException; 18 | import org.objectweb.asm.tree.analysis.BasicValue; 19 | import org.objectweb.asm.tree.analysis.SimpleVerifier; 20 | import org.objectweb.asm.util.CheckClassAdapter; 21 | 22 | import java.io.IOException; 23 | import java.io.OutputStream; 24 | import java.io.PrintWriter; 25 | import java.lang.instrument.ClassDefinition; 26 | import java.lang.reflect.Method; 27 | import java.util.ArrayList; 28 | import java.util.Iterator; 29 | import java.util.List; 30 | 31 | public class MixinPlugin { 32 | 33 | @Getter 34 | private ObfMap obfMap; 35 | @Getter 36 | private Plugin plugin; 37 | 38 | private List registeredMixins = new ArrayList<>(); 39 | 40 | public MixinPlugin(Plugin plugin, ObfMap obfMap) { 41 | this.plugin = plugin; 42 | this.obfMap = obfMap; 43 | } 44 | 45 | @SneakyThrows 46 | public Method getMethod(Class clazz, String methodName, Class returnType, Class... arguments) { 47 | String original = obfMap.resolveMapping(new ObfMap.MethodMapping(clazz.getName().replace(".", "/"), ASMUtils.getDescriptor(returnType, arguments), methodName)); 48 | return clazz.getDeclaredMethod(original, arguments); 49 | } 50 | 51 | @SneakyThrows 52 | public boolean registerMixin(String namespace, MixinAction mixinAction, Class owner, String deObfMethodName, Class returnType, Class... arguments) { 53 | if (registeredMixins.contains(namespace)) { 54 | if (MixBukkit.DEBUG) { 55 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Mixin with namespace: " + namespace + " is already registered! Skipping..."); 56 | } 57 | return false; 58 | } 59 | if (MixBukkit.DEBUG) { 60 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Registering Mixin: " + plugin.getName() + ":" + namespace); 61 | } 62 | String descriptor = ASMUtils.getDescriptor(returnType, arguments); 63 | String obfMethodName = obfMap.resolveMapping(new ObfMap.MethodMapping(owner.getName().replace(".", "/"), descriptor, deObfMethodName)); 64 | if (MixBukkit.DEBUG) { 65 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Obfuscated method name: " + owner.getName().replace(".", "/") + "." + obfMethodName + descriptor); 66 | } 67 | 68 | ClassNode classNode = ClassesManager.getClassNode(owner.getName()); 69 | if (classNode == null) { 70 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not find the target class: " + owner.getName()); 71 | return false; 72 | } 73 | PrintWriter printWriter = new PrintWriter(MixBukkit.ERROR_OUTPUT_STREAM, true); 74 | for (MethodNode method : classNode.methods) { 75 | // System.out.println(method.name + method.desc + " / " + obfMethodName + descriptor); 76 | if (method.name.equals(obfMethodName) && method.desc.equals(descriptor)) { 77 | if (MixBukkit.DEBUG) { 78 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Found Method to hook!"); 79 | } 80 | 81 | if (MixBukkit.DEBUG) { 82 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Processing..."); 83 | } 84 | mixinAction.action(owner, method); 85 | 86 | 87 | 88 | if (MixBukkit.DEBUG) { 89 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Assembling..."); 90 | } 91 | byte[] data = ASMUtils.fromClassNode(classNode); 92 | 93 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Verifying..."); 94 | ClassReader classReader = new ClassReader(data); 95 | boolean[] illegal = new boolean[] {false}; 96 | CheckClassAdapter.verify(classReader, getClass().getClassLoader().getParent(), false, new PrintWriter(new OutputStream() { 97 | @Override 98 | public void write(int b) throws IOException { 99 | illegal[0] = true; 100 | } 101 | })); 102 | 103 | if (illegal[0]) { 104 | if (MixBukkit.DEBUG) { 105 | printWriter.println("Mixin Method:"); 106 | CustomTextifier methodVisitor = new CustomTextifier(); 107 | method.accept(methodVisitor); 108 | for (Object o : methodVisitor.text) { 109 | printWriter.println(o); 110 | } 111 | printWriter.println(""); 112 | printWriter.println(""); 113 | CheckClassAdapter.verify(classReader, getClass().getClassLoader().getParent(), false, printWriter); 114 | } 115 | if (MixBukkit.SAFE_MODE) { 116 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Invalid Bytecode, and safe-mode is on"); 117 | return false; 118 | } else { 119 | Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[?] Mixin: " + plugin.getName() + ":" + namespace + " has failed the verification, and it might crash your server! Be careful."); 120 | } 121 | } 122 | 123 | try { 124 | if (MixBukkit.DEBUG) { 125 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Redefining class..."); 126 | } 127 | 128 | MixBukkit.INSTRUMENTATION.redefineClasses(new ClassDefinition(owner, data)); 129 | ClassesManager.classNodes.put(owner.getName(), classNode); 130 | ClassesManager.classes.put(owner.getName(), data); 131 | } catch (Exception e) { 132 | e.printStackTrace(); 133 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not redefine class: " + owner.getSimpleName()); 134 | } 135 | if (MixBukkit.DEBUG) { 136 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Successfully hooked " + namespace); 137 | } 138 | registeredMixins.add(namespace); 139 | return true; 140 | } 141 | } 142 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Failed to load mixin: " + plugin.getName() + ":" + namespace + ", Reason: Could not find the target method"); 143 | return false; 144 | } 145 | 146 | public static void verify(ClassReader classReader, ClassLoader loader, PrintWriter printWriter) { 147 | ClassNode classNode = new ClassNode(); 148 | classReader.accept(new CheckClassAdapter(Opcodes.ASM9, classNode, true) { 149 | }, 2); 150 | Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName); 151 | List methods = classNode.methods; 152 | List interfaces = new ArrayList(); 153 | Iterator var8 = classNode.interfaces.iterator(); 154 | 155 | while(var8.hasNext()) { 156 | String interfaceName = (String)var8.next(); 157 | interfaces.add(Type.getObjectType(interfaceName)); 158 | } 159 | 160 | var8 = methods.iterator(); 161 | 162 | while(var8.hasNext()) { 163 | MethodNode method = (MethodNode)var8.next(); 164 | SimpleVerifier verifier = new SimpleVerifier(Type.getObjectType(classNode.name), syperType, interfaces, (classNode.access & 512) != 0); 165 | Analyzer analyzer = new Analyzer(verifier); 166 | if (loader != null) { 167 | verifier.setClassLoader(loader); 168 | } 169 | 170 | try { 171 | analyzer.analyze(classNode.name, method); 172 | } catch (AnalyzerException var13) { 173 | var13.printStackTrace(printWriter); 174 | } 175 | 176 | } 177 | 178 | printWriter.flush(); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/ObfMap.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.ChatColor; 9 | 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Scanner; 14 | 15 | public class ObfMap { 16 | 17 | // Deobf -> obf 18 | private Map fieldMappings = new HashMap<>(); 19 | private Map methodMappings = new HashMap<>(); 20 | 21 | public ObfMap(InputStream memberMap) { 22 | if (memberMap == null) { 23 | if (MixBukkit.DEBUG) { 24 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Mapping is null"); 25 | } 26 | return; 27 | } 28 | Scanner scanner = new Scanner(memberMap); 29 | while (scanner.hasNextLine()) { 30 | String line = scanner.nextLine(); 31 | if (line.startsWith("#")) continue; 32 | String[] s = line.split(" "); 33 | if (s.length == 4) { // Method mapping, 34 | methodMappings.put(new MethodMapping(s[0], s[2], s[3]), s[1]); 35 | } else if (s.length == 3) { // Field mapping, 36 | fieldMappings.put(new FieldMapping(s[0], s[2]), s[1]); 37 | } else { 38 | System.out.println("Illegal Mapping: " + line + " (Length: " + s.length + ")"); 39 | } 40 | } 41 | } 42 | 43 | public String resolveMapping(FieldMapping fieldMapping) { 44 | String s = fieldMappings.get(fieldMapping); 45 | if (s == null) return fieldMapping.getFieldName(); 46 | return s; 47 | } 48 | 49 | public String resolveMapping(MethodMapping methodMapping) { 50 | String s = methodMappings.get(methodMapping); 51 | if (s == null) return methodMapping.getMethodName(); 52 | return s; 53 | } 54 | 55 | @Getter 56 | @EqualsAndHashCode 57 | @ToString 58 | public static class FieldMapping { 59 | private final String ownerName; // Replaced / with . 60 | private final String fieldName; 61 | 62 | public FieldMapping(String ownerName, String fieldName) { 63 | this.ownerName = ownerName.replace(".", "/"); 64 | this.fieldName = fieldName; 65 | } 66 | } 67 | 68 | @Getter 69 | @EqualsAndHashCode 70 | @ToString 71 | public static class MethodMapping { 72 | private final String ownerName; // Replaced / with . 73 | private final String descriptor; 74 | private final String methodName; 75 | 76 | public MethodMapping(String ownerName, String descriptor, String methodName) { 77 | this.ownerName = ownerName.replace(".", "/"); 78 | this.descriptor = descriptor; 79 | this.methodName = methodName; 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/MixinAction.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action; 2 | 3 | import org.objectweb.asm.tree.InsnList; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | public interface MixinAction { 7 | 8 | void action(Class owner, MethodNode method); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionCallSuper.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 4 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import org.objectweb.asm.tree.MethodNode; 9 | import org.objectweb.asm.tree.VarInsnNode; 10 | 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Modifier; 13 | 14 | public class MActionCallSuper implements MixinAction { 15 | 16 | private MixinPlugin plugin; 17 | 18 | public MActionCallSuper(MixinPlugin plugin) { 19 | this.plugin = plugin; 20 | } 21 | 22 | @Override 23 | public void action(Class owner, MethodNode method) { 24 | Method m = null; 25 | Method superMethod = null; 26 | for (Method declaredMethod : owner.getDeclaredMethods()) { 27 | if (declaredMethod.getName().equals(method.name)) { 28 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()).equals(method.desc)) { 29 | m = declaredMethod; 30 | } 31 | } 32 | } 33 | if (m != null) { 34 | Class superclass = owner.getSuperclass(); 35 | while (superclass != null) { 36 | for (Method declaredMethod : superclass.getDeclaredMethods()) { 37 | if (declaredMethod.getName().equals(method.name)) { 38 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()).equals(method.desc)) { 39 | superMethod = declaredMethod; 40 | } 41 | } 42 | } 43 | superclass = superclass.getSuperclass(); 44 | } 45 | } 46 | if (superMethod == null) { 47 | throw new IllegalArgumentException("Could not find super method in " + owner.getSimpleName()); 48 | } 49 | method.instructions.clear(); 50 | int varNum = 0; 51 | if (Modifier.isStatic(superMethod.getModifiers())) { 52 | method.instructions.add(new VarInsnNode(Opcode.ALOAD, varNum++)); 53 | } 54 | Class[] parameterTypes = superMethod.getParameterTypes(); 55 | for (int i = 0; i < parameterTypes.length; i++) { 56 | method.instructions.add(ASMUtils.loadVar(parameterTypes[i], varNum++)); 57 | } 58 | method.instructions.add(new IShellCodeMethodInvoke(superMethod).generate()); 59 | method.instructions.add(ASMUtils.genReturnNode(superMethod.getReturnType())); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionInsertShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.ChatColor; 11 | import org.objectweb.asm.tree.InsnList; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | import java.util.List; 15 | 16 | @AllArgsConstructor 17 | @Getter 18 | public class MActionInsertShellCode implements MixinAction { 19 | 20 | private ShellCode shellCode; 21 | private HookLocator hookLocator; 22 | 23 | 24 | @Override 25 | public void action(Class owner, MethodNode method) { 26 | // Copy hookLocator.getLineNumber(method) to listHooks 27 | List hooks = hookLocator.getLineNumber(method.instructions); 28 | 29 | LocalVarManager localVarManager = new LocalVarManager(method); 30 | 31 | // Hook! 32 | InsnList newInstructions = new InsnList(); 33 | for (int i = 0; i < method.instructions.size(); i++) { 34 | if (hooks.contains(i)) { 35 | if (shellCode.getShellCodeInfo().calledDirectly()) { 36 | try { 37 | InsnList instructions = shellCode.generate(method, localVarManager); 38 | newInstructions.add(instructions); 39 | newInstructions.add(shellCode.popExtraStack()); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo().name() + ChatColor.RED + "\" has failed generating instructions: Exception Thrown"); 43 | } 44 | } else { 45 | Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo().name() + ChatColor.RED + "\" shouldn't be called directly (calledDirectly = false)"); 46 | } 47 | 48 | } 49 | newInstructions.add(method.instructions.get(i)); 50 | } 51 | method.instructions = newInstructions; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodCallSpoofer.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 5 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 6 | import javassist.bytecode.Opcode; 7 | import lombok.SneakyThrows; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.UUID; 14 | import java.util.function.Predicate; 15 | 16 | @SuppressWarnings("Untested") 17 | public class MActionMethodCallSpoofer implements MixinAction { 18 | 19 | private Method method; 20 | private String key; 21 | private Predicate filter; 22 | 23 | private static Map getters = new HashMap<>(); 24 | 25 | public static Object get(String value) { 26 | return getters.get(value).getReturnValue(); 27 | } 28 | 29 | private MActionMethodCallSpoofer(Method method, ReturnValueGetter returnValueGetter, Predicate filter) { 30 | this.method = method; 31 | if (method.getReturnType() == void.class) throw new IllegalArgumentException("Method: " + method.getName() + " is not returning anything. Nothing to spoof"); 32 | UUID uuid = UUID.randomUUID(); 33 | while (getters.containsKey(uuid.toString())) { 34 | uuid = UUID.randomUUID(); 35 | System.out.println("Come on! You are so f**king lucky! You literally got same UUID, how?"); 36 | System.out.println("If you see this message without cheating, today is literally your most luckiest day"); 37 | System.out.println("I would say the chance of it is lower than every thing you can think of"); 38 | System.out.println("UUID: " + uuid); 39 | } 40 | getters.put(uuid.toString(), returnValueGetter); 41 | this.key = uuid.toString(); 42 | } 43 | 44 | @Override 45 | @SneakyThrows 46 | public void action(Class owner, MethodNode methodNode) { 47 | InsnList out = new InsnList(); 48 | int amount = 0; 49 | for (AbstractInsnNode instruction : methodNode.instructions) { 50 | out.add(instruction); 51 | if (instruction instanceof MethodInsnNode) { 52 | MethodInsnNode insn = (MethodInsnNode) instruction; 53 | if (insn.name.equals(method.getName()) && insn.owner.equals(method.getDeclaringClass().getName().replace(".", "/")) 54 | && insn.desc.equals(ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes())) && filter.test(amount++)) { 55 | out.add(new InsnNode(Opcode.POP)); // Pop the return value first 56 | out.add(new LdcInsnNode(key)); 57 | out.add(new IShellCodeReflectionMethodInvoke(MActionMethodCallSpoofer.class.getDeclaredMethod("get", String.class)).generate()); 58 | out.add(ASMUtils.cast(method.getReturnType())); 59 | } 60 | } 61 | } 62 | methodNode.instructions = out; 63 | } 64 | 65 | public interface ReturnValueGetter { 66 | Object getReturnValue(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodReplacer.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.SneakyThrows; 9 | import org.objectweb.asm.tree.InsnList; 10 | import org.objectweb.asm.tree.InsnNode; 11 | import org.objectweb.asm.tree.MethodNode; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | public class MActionMethodReplacer implements MixinAction { 16 | 17 | private Method handler; 18 | 19 | public MActionMethodReplacer(Method handler) { 20 | this.handler = handler; 21 | } 22 | 23 | @Override 24 | @SneakyThrows 25 | public void action(Class owner, MethodNode methodNode) { 26 | methodNode.tryCatchBlocks.clear(); 27 | methodNode.localVariables.clear(); 28 | methodNode.instructions.clear(); 29 | Class returnType = ASMUtils.getReturnType(methodNode.desc); 30 | if (returnType != handler.getReturnType()) throw new IllegalArgumentException("Handler: " + handler.getName() + " is not returning same type as target method (" + handler.getReturnType().getName() + "(return type of handler) != " + returnType.getName() + "(return type of target) )"); 31 | LocalVarManager varManager = new LocalVarManager(methodNode); 32 | InsnList out = new InsnList(); 33 | Class[] parameterTypes = handler.getParameterTypes(); 34 | for (int i = 0; i < parameterTypes.length; i++) { 35 | Class parameterType = parameterTypes[i]; 36 | out.add(ASMUtils.castToObject(i, parameterType)); 37 | } 38 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler); 39 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager)); 40 | if (!methodNode.desc.endsWith("V")) { 41 | out.add(ASMUtils.cast(returnType)); 42 | out.add(ASMUtils.genReturnNode(returnType)); 43 | } else { 44 | out.add(new InsnNode(Opcode.RETURN)); 45 | } 46 | methodNode.instructions = out; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionPipeline.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | public class MActionPipeline implements MixinAction { 7 | 8 | private final MixinAction[] actions; 9 | 10 | public MActionPipeline(MixinAction... actions) { 11 | this.actions = actions; 12 | } 13 | 14 | @Override 15 | public void action(Class owner, MethodNode method) { 16 | for (MixinAction action : actions) { 17 | action.action(owner, method); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/HookLocator.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator; 2 | 3 | import org.objectweb.asm.tree.InsnList; 4 | 5 | import java.util.List; 6 | 7 | public interface HookLocator { 8 | 9 | /** 10 | * Process a method, and return the line number to hook at. Return empty array if it fails to find target hook location 11 | * @param insnList MethodNode input 12 | * @return Array of line numbers, it will be inserted, means 0 will make first instruction into hooking instruction 13 | */ 14 | List getLineNumber(InsnList insnList); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldAccess.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import lombok.AllArgsConstructor; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.InsnList; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.function.Predicate; 14 | 15 | @AllArgsConstructor 16 | public class HLocatorFieldAccess implements HookLocator { 17 | 18 | private Field field; 19 | private PostPreState state; 20 | private Predicate filter; 21 | 22 | @Override 23 | public List getLineNumber(InsnList insnList) { 24 | List out = new ArrayList<>(); 25 | int amount = 0; 26 | for (int i = 0; i < insnList.size(); i++) { 27 | if (insnList.get(i) instanceof FieldInsnNode) { 28 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i); 29 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 30 | String name = field.getName(); 31 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 32 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 33 | out.add(i + (state == PostPreState.POST?1:0)); 34 | } 35 | } 36 | } 37 | return out; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldRead.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import javassist.bytecode.Opcode; 7 | import lombok.AllArgsConstructor; 8 | import org.objectweb.asm.tree.FieldInsnNode; 9 | import org.objectweb.asm.tree.InsnList; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | @AllArgsConstructor 17 | public class HLocatorFieldRead implements HookLocator { 18 | 19 | private Field field; 20 | private PostPreState state; 21 | private Predicate filter; 22 | 23 | @Override 24 | public List getLineNumber(InsnList insnList) { 25 | List out = new ArrayList<>(); 26 | int amount = 0; 27 | for (int i = 0; i < insnList.size(); i++) { 28 | if (insnList.get(i) instanceof FieldInsnNode) { 29 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i); 30 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 31 | String name = field.getName(); 32 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 33 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 34 | if (fieldInsnNode.getOpcode() == Opcode.GETFIELD || fieldInsnNode.getOpcode() == Opcode.GETSTATIC) 35 | out.add(i + (state == PostPreState.POST?1:0)); 36 | } 37 | } 38 | } 39 | return out; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldWrite.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import javassist.bytecode.Opcode; 7 | import lombok.AllArgsConstructor; 8 | import org.objectweb.asm.tree.FieldInsnNode; 9 | import org.objectweb.asm.tree.InsnList; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | @AllArgsConstructor 17 | public class HLocatorFieldWrite implements HookLocator { 18 | 19 | private Field field; 20 | private PostPreState state; 21 | private Predicate filter; 22 | 23 | @Override 24 | public List getLineNumber(InsnList insnList) { 25 | List out = new ArrayList<>(); 26 | int amount = 0; 27 | for (int i = 0; i < insnList.size(); i++) { 28 | if (insnList.get(i) instanceof FieldInsnNode) { 29 | FieldInsnNode fieldInsnNode = (FieldInsnNode) insnList.get(i); 30 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 31 | String name = field.getName(); 32 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 33 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 34 | if (fieldInsnNode.getOpcode() == Opcode.PUTFIELD || fieldInsnNode.getOpcode() == Opcode.PUTSTATIC) 35 | out.add(i + (state == PostPreState.POST?1:0)); 36 | } 37 | } 38 | } 39 | return out; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorHead.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import org.objectweb.asm.tree.InsnList; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class HLocatorHead implements HookLocator { 10 | @Override 11 | public List getLineNumber(InsnList insnNodes) { 12 | return Arrays.asList(0); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import org.objectweb.asm.tree.InsnList; 7 | import org.objectweb.asm.tree.MethodInsnNode; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.function.Predicate; 13 | 14 | public class HLocatorMethodInvoke implements HookLocator { 15 | 16 | private String owner; 17 | private String desc; 18 | private String name; 19 | private PostPreState state; 20 | private Predicate filter; 21 | 22 | public HLocatorMethodInvoke(Method method, PostPreState state, Predicate filter) { 23 | owner = method.getDeclaringClass().getName().replace(".", "/"); 24 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()); 25 | name = method.getName(); 26 | this.filter = filter; 27 | this.state = state; 28 | } 29 | 30 | public HLocatorMethodInvoke(Class owner, Method method, PostPreState state, Predicate filter) { 31 | this.owner = owner.getName().replace(".", "/"); 32 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()); 33 | name = method.getName(); 34 | this.filter = filter; 35 | this.state = state; 36 | } 37 | 38 | @Override 39 | public List getLineNumber(InsnList insnList) { 40 | int amount = 0; 41 | List out = new ArrayList<>(); 42 | for (int i = 0; i < insnList.size(); i++) { 43 | if (insnList.get(i) instanceof MethodInsnNode) { 44 | MethodInsnNode insnNode = (MethodInsnNode) insnList.get(i); 45 | if (insnNode.owner.equals(owner) && insnNode.name.equals(name) && insnNode.desc.equals(desc) && filter.test(amount++)) { 46 | out.add(i + (state == PostPreState.POST?1:0)); 47 | } 48 | } 49 | } 50 | return out; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorReturn.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | import org.objectweb.asm.tree.InsnList; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class HLocatorReturn implements HookLocator { 11 | @Override 12 | public List getLineNumber(InsnList insnList) { 13 | List list = new ArrayList<>(); 14 | for (int i = 0; i < insnList.size(); i++) { 15 | AbstractInsnNode insnNode = insnList.get(i); 16 | if (insnNode.getOpcode() >= 172 && insnNode.getOpcode() <= 177) { 17 | list.add(i); 18 | } 19 | } 20 | return list; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/IShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import org.objectweb.asm.tree.InsnList; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | public interface IShellCode { 7 | 8 | InsnList generate(MethodNode methodNode, LocalVarManager varManager); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/LocalVarManager.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 4 | import lombok.Getter; 5 | import org.objectweb.asm.tree.MethodNode; 6 | 7 | public class LocalVarManager { 8 | 9 | @Getter 10 | private MethodNode methodNode; 11 | 12 | private int latestVarNumber; 13 | 14 | public LocalVarManager(MethodNode methodNode) { 15 | this.methodNode = methodNode; 16 | latestVarNumber = ASMUtils.getLatestVarNumber(methodNode.instructions) + 1; 17 | } 18 | 19 | public int allocateVarNumber() { 20 | return latestVarNumber++; 21 | } 22 | 23 | public int getLatestUnusedVarNumber() { 24 | return latestVarNumber; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import org.objectweb.asm.Opcodes; 4 | import org.objectweb.asm.tree.InsnList; 5 | import org.objectweb.asm.tree.InsnNode; 6 | 7 | public abstract class ShellCode implements IShellCode { 8 | 9 | public ShellCode() { 10 | if (getShellCodeInfo() == null) throw new NullPointerException("Shellcode: " + this.getClass().getName() + " is invalid! @ShellCodeInfo annotation is not presented."); 11 | } 12 | 13 | public InsnList generate() { 14 | return generate(null, null); 15 | } 16 | 17 | public InsnList popExtraStack() { 18 | InsnList list = new InsnList(); 19 | for (int i = 0; i < getShellCodeInfo().stacksContent().length; i++) { 20 | list.add(new InsnNode(Opcodes.POP)); 21 | } 22 | return list; 23 | } 24 | 25 | public ShellCodeInfo getShellCodeInfo() { 26 | try { 27 | return getClass().getAnnotationsByType(ShellCodeInfo.class)[0]; 28 | } catch (Exception ignored) { 29 | return null; 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCodeInfo.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 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 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ShellCodeInfo { 11 | 12 | /** 13 | * @return Official name of the shellcode 14 | */ 15 | String name(); 16 | 17 | /** 18 | * @return Description of the shellcode, like what it will do etc. 19 | */ 20 | String description(); 21 | 22 | /** 23 | * @return Returns true if it's going to modify the MethodNode. For example: Add a try & catch block 24 | */ 25 | boolean requireMethodNodeModification() default false; 26 | 27 | /** 28 | * @return Returns true if it will need to allocate new variables 29 | */ 30 | boolean requireVarManager() default false; 31 | 32 | /** 33 | * @return Content of stack, first one pushed into stack should be in the bottom of return value 34 | */ 35 | String[] stacksContent() default {}; 36 | 37 | /** 38 | * @return What's required on the stack 39 | */ 40 | String[] requiredStacksContent() default {}; 41 | 42 | /** 43 | * @return Is it safe to call it directly inside a method 44 | */ 45 | boolean calledDirectly() default false; 46 | 47 | /** 48 | * @return Is it required to use -noverify in order to load the shellcode 49 | */ 50 | boolean failsClassVerification() default true; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/CallbackInfo.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeLoadClassFromPCL; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 8 | import javassist.bytecode.Opcode; 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | import lombok.SneakyThrows; 12 | import org.objectweb.asm.tree.*; 13 | 14 | public class CallbackInfo { 15 | 16 | @Getter 17 | @Setter 18 | private Object returnValue = null; 19 | @Getter 20 | @Setter 21 | private boolean returned; 22 | 23 | @SneakyThrows 24 | public static InsnList generateCallBackInfo() { 25 | InsnList out = new InsnList(); 26 | out.add(new IShellCodeLoadClassFromPCL(CallbackInfo.class).generate()); 27 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("newInstance")).generate()); 28 | return out; 29 | } 30 | 31 | @SneakyThrows 32 | public static InsnList processCallBackInfo(MethodNode hookedMethod, LocalVarManager varManager, int varLocation) { 33 | InsnList out = new InsnList(); 34 | LabelNode returnBranch = new LabelNode(); 35 | LabelNode defaultBranch = new LabelNode(); 36 | 37 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation)); 38 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("isReturned")).generate(null, varManager)); 39 | out.add(new TypeInsnNode(Opcode.CHECKCAST, Boolean.class.getName().replace(".", "/"))); 40 | out.add(new IShellCodeMethodInvoke(Boolean.class.getDeclaredMethod("booleanValue")).generate()); 41 | out.add(new JumpInsnNode(Opcode.IFEQ, defaultBranch)); 42 | out.add(returnBranch); 43 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation)); 44 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("getReturnValue")).generate(null, varManager)); 45 | if (!hookedMethod.desc.endsWith("V")) { 46 | Class returnType = ASMUtils.getReturnType(hookedMethod.desc); 47 | out.add(ASMUtils.cast(returnType)); 48 | out.add(ASMUtils.genReturnNode(returnType)); 49 | } else { 50 | out.add(new InsnNode(Opcode.RETURN)); 51 | } 52 | 53 | out.add(defaultBranch); 54 | return out; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeComment.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.NonNull; 9 | import lombok.RequiredArgsConstructor; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.LdcInsnNode; 13 | import org.objectweb.asm.tree.MethodNode; 14 | 15 | @ShellCodeInfo( 16 | name = "Comment", 17 | description = "Leave a comment, and it won't do anything other than wasting performance", 18 | calledDirectly = true 19 | ) 20 | @RequiredArgsConstructor 21 | public class ShellCodeComment extends ShellCode { 22 | 23 | @NonNull 24 | private String comment; 25 | 26 | @Override 27 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 28 | return ASMUtils.asInsnList( 29 | new LdcInsnNode(comment), 30 | new InsnNode(Opcode.POP) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintMessage.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import static javassist.bytecode.Opcode.GETSTATIC; 11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL; 12 | 13 | @AllArgsConstructor 14 | @Getter 15 | @ShellCodeInfo( 16 | name = "stdout Print Message", 17 | description = "Call a System.out.println", 18 | requireVarManager = false, 19 | stacksContent = {}, 20 | requiredStacksContent = {}, 21 | calledDirectly = true 22 | ) 23 | public class ShellCodePrintMessage extends ShellCode { 24 | 25 | private String message; 26 | 27 | 28 | @Override 29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 30 | InsnList list = new InsnList(); 31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 32 | list.add(new LdcInsnNode(message)); 33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 34 | return list; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintTopStackType.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import static javassist.bytecode.Opcode.GETSTATIC; 11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL; 12 | 13 | @AllArgsConstructor 14 | @Getter 15 | @ShellCodeInfo( 16 | name = "Print Type", 17 | description = "System.out the full class name of the content that's on the top of the stack", 18 | requireVarManager = false, 19 | stacksContent = {}, 20 | requiredStacksContent = {}, 21 | calledDirectly = true 22 | ) 23 | public class ShellCodePrintTopStackType extends ShellCode { 24 | 25 | private String message; 26 | 27 | 28 | @Override 29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 30 | InsnList list = new InsnList(); 31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 32 | list.add(new LdcInsnNode(message)); 33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 34 | return list; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeReflectionMixinPluginMethodCall.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 8 | import lombok.AllArgsConstructor; 9 | import lombok.SneakyThrows; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.MethodNode; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | @ShellCodeInfo( 16 | name = "Reflection Mixin Plugin Method Call", 17 | description = "It will use a URLClassLoader of a random plugin to load the target plugin class and invoke the method. " + 18 | "A regular method invoke doesn't work since it uses PluginClassLoader.", 19 | requireVarManager = true, 20 | requireMethodNodeModification = true, 21 | stacksContent = {"Return value of invoked method (as Object)"}, 22 | requiredStacksContent = {}, 23 | calledDirectly = true 24 | ) 25 | @AllArgsConstructor 26 | public class ShellCodeReflectionMixinPluginMethodCall extends ShellCode { 27 | 28 | private Method handler; 29 | 30 | @Override 31 | @SneakyThrows 32 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 33 | InsnList out = new InsnList(); 34 | Class[] parameterTypes = handler.getParameterTypes(); 35 | boolean hasCallBackInfo = false; 36 | for (int i = 0; i < parameterTypes.length; i++) { 37 | Class parameterType = parameterTypes[i]; 38 | if (CallbackInfo.class.isAssignableFrom(parameterType) && i == parameterTypes.length - 1) { 39 | out.add(CallbackInfo.generateCallBackInfo()); 40 | hasCallBackInfo = true; 41 | } else { 42 | out.add(ASMUtils.castToObject(i, parameterType)); 43 | } 44 | } 45 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler); 46 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager)); 47 | if (hasCallBackInfo) { 48 | Integer varLocation = shellCodeReflectionMethodInvoke.getArgumentVarIndex().get(parameterTypes.length - 1); 49 | out.add(CallbackInfo.processCallBackInfo(methodNode, varManager, varLocation)); 50 | } 51 | return out; 52 | } 53 | 54 | 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeLoadClassFromPCL.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import javassist.bytecode.Opcode; 7 | import lombok.SneakyThrows; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.plugin.PluginManager; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.LdcInsnNode; 13 | import org.objectweb.asm.tree.MethodNode; 14 | 15 | @ShellCodeInfo( 16 | name = "Load Class From Plugin Class Loader", 17 | description = "Load a class from the plugin class loader, and get its \"class\" instance", 18 | stacksContent = {"Class"}, 19 | calledDirectly = true 20 | ) 21 | public class IShellCodeLoadClassFromPCL extends ShellCode { 22 | 23 | private String name; 24 | 25 | public IShellCodeLoadClassFromPCL(String className) { 26 | this.name = className.replace("/", "."); 27 | } 28 | 29 | public IShellCodeLoadClassFromPCL(Class clazz) { 30 | this.name = clazz.getName(); 31 | } 32 | 33 | @Override 34 | @SneakyThrows 35 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 36 | InsnList out = new InsnList(); 37 | out.add(new LdcInsnNode(name)); 38 | out.add(new IShellCodePushInt(1).generate()); // true 39 | out.add(new IShellCodeMethodInvoke(Bukkit.class.getDeclaredMethod("getPluginManager")).generate()); 40 | out.add(new IShellCodeMethodInvoke(PluginManager.class.getDeclaredMethod("getPlugins")).generate()); 41 | out.add(new IShellCodePushInt(0).generate()); 42 | out.add(new InsnNode(Opcode.AALOAD)); 43 | out.add(new IShellCodeMethodInvoke(Object.class.getDeclaredMethod("getClass")).generate()); 44 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("getClassLoader")).generate()); 45 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("forName", String.class, boolean.class, ClassLoader.class)).generate()); 46 | return out; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.MethodInsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | 17 | @ShellCodeInfo( 18 | name = "Method Invoke", 19 | description = "Call methods programmatically", 20 | stacksContent = {"Return value of invoked method"}, 21 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"} 22 | ) 23 | @AllArgsConstructor 24 | @Getter 25 | public class IShellCodeMethodInvoke extends ShellCode { 26 | 27 | private Method method; 28 | 29 | @Override 30 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 31 | InsnList list = new InsnList(); 32 | list.add(new MethodInsnNode( 33 | Modifier.isStatic(method.getModifiers()) ? Opcode.INVOKESTATIC:(method.getDeclaringClass().isInterface()?Opcode.INVOKEINTERFACE:Opcode.INVOKEVIRTUAL), 34 | method.getDeclaringClass().getName().replace(".", "/"), 35 | method.getName(), 36 | ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()), 37 | method.getDeclaringClass().isInterface() && !Modifier.isStatic(method.getModifiers()) 38 | )); 39 | return list; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeNewArrayAndAddContent.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | import org.objectweb.asm.tree.TypeInsnNode; 14 | 15 | @ShellCodeInfo( 16 | name = "New Array and Add Content", 17 | description = "Create a new array, set the content of it (by for looping), and pushes the array to stack", 18 | requireVarManager = false, 19 | stacksContent = {"The created array"}, 20 | requiredStacksContent = {}, 21 | calledDirectly = false 22 | ) 23 | @AllArgsConstructor 24 | @Getter 25 | public class IShellCodeNewArrayAndAddContent extends ShellCode { 26 | 27 | private int arraySize; 28 | private Class type; 29 | private ForLoopInstructionGenerator gen; 30 | 31 | @Override 32 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 33 | InsnList out = new InsnList(); 34 | out.add(ASMUtils.pushInt(arraySize)); 35 | out.add(new TypeInsnNode(Opcode.ANEWARRAY, type.getName().replace(".", "/"))); 36 | for (int i = 0; i < arraySize; i++) { 37 | out.add(new InsnNode(Opcode.DUP)); 38 | out.add(ASMUtils.pushInt(i)); 39 | out.add(gen.generate(i)); 40 | out.add(new InsnNode(Opcode.AASTORE)); 41 | } 42 | return out; 43 | } 44 | 45 | public interface ForLoopInstructionGenerator { 46 | InsnList generate(int index); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodePushInt.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import javassist.bytecode.Opcode; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import org.objectweb.asm.tree.InsnList; 10 | import org.objectweb.asm.tree.InsnNode; 11 | import org.objectweb.asm.tree.IntInsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | @ShellCodeInfo( 15 | name = "Push Int", 16 | description = "Push an int into stack" 17 | ) 18 | @AllArgsConstructor 19 | @Getter 20 | public class IShellCodePushInt extends ShellCode { 21 | 22 | private int value; 23 | 24 | @Override 25 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 26 | InsnList out = new InsnList(); 27 | if (value <= 5 && value >= -1) { 28 | out.add(new InsnNode(value + 3)); 29 | return out; 30 | } 31 | if (value < 255 & value > 0) { 32 | out.add(new IntInsnNode(Opcode.BIPUSH, value)); 33 | return out; 34 | } 35 | out.add(new IntInsnNode(Opcode.SIPUSH, value)); 36 | return out; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeReflectionMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.Getter; 9 | import lombok.SneakyThrows; 10 | import org.bukkit.Bukkit; 11 | import org.objectweb.asm.tree.*; 12 | 13 | import java.lang.reflect.Method; 14 | import java.lang.reflect.Modifier; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | @ShellCodeInfo( 20 | name = "Reflection Method Invoke", 21 | description = "Invoke a method with similar usage as ShellCodeMethodInvoke", 22 | stacksContent = {"Return value of invoked method"}, 23 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"} 24 | ) 25 | @Getter 26 | public class IShellCodeReflectionMethodInvoke extends ShellCode { 27 | 28 | private Method method; 29 | private List argumentVarIndex = new ArrayList<>(); 30 | 31 | public IShellCodeReflectionMethodInvoke(Method method) { 32 | this.method = method; 33 | } 34 | 35 | @SneakyThrows 36 | private static void action() { 37 | Class.forName("CLASS_NAME_HERE", true, Bukkit.getPluginManager().getPlugins()[0].getClass().getClassLoader()) 38 | .getDeclaredMethod("methodNameHere", int.class, float.class, double.class, char.class, boolean.class, byte.class, short.class, String.class, String[].class, int[].class) 39 | .invoke(null); 40 | } 41 | 42 | @Override 43 | @SneakyThrows 44 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 45 | InsnList out = new InsnList(); 46 | argumentVarIndex = new ArrayList<>(); 47 | int obj = -1; 48 | if (!Modifier.isStatic(method.getModifiers())) { 49 | obj = varManager.allocateVarNumber(); 50 | out.add(new VarInsnNode(Opcode.ASTORE, obj)); 51 | } 52 | int length = method.getParameterTypes().length; 53 | for (int i = 0; i < length; i++) { 54 | int arg = varManager.allocateVarNumber(); 55 | argumentVarIndex.add(arg); 56 | out.add(new VarInsnNode(Opcode.ASTORE, arg)); 57 | } 58 | Collections.reverse(argumentVarIndex); 59 | 60 | out.add(new IShellCodeLoadClassFromPCL(method.getDeclaringClass()).generate()); 61 | out.add(new LdcInsnNode(method.getName())); 62 | out.add(new IShellCodeNewArrayAndAddContent(method.getParameterTypes().length, Class.class, index -> { 63 | InsnList list = new InsnList(); 64 | list.add(ASMUtils.generateGetClassNode(method.getParameterTypes()[index])); 65 | return list; 66 | }).generate()); 67 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class)).generate()); 68 | if (obj == -1) { 69 | out.add(new InsnNode(Opcode.ACONST_NULL)); 70 | } else { 71 | out.add(new VarInsnNode(Opcode.ALOAD, obj)); 72 | } 73 | out.add(new IShellCodeNewArrayAndAddContent(argumentVarIndex.size(), Object.class, index -> { 74 | InsnList list = new InsnList(); 75 | try { 76 | list.add(new VarInsnNode(Opcode.ALOAD, argumentVarIndex.get(index))); 77 | } catch (Exception ignored) {} 78 | return list; 79 | }).generate()); 80 | out.add(new IShellCodeMethodInvoke(Method.class.getDeclaredMethod("invoke", Object.class, Object[].class)).generate()); 81 | 82 | return out; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/utils/ASMUtils.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeLoadClassFromPCL; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke; 6 | import javassist.CtClass; 7 | import javassist.bytecode.Opcode; 8 | import lombok.SneakyThrows; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.ChatColor; 11 | import org.objectweb.asm.ClassReader; 12 | import org.objectweb.asm.ClassWriter; 13 | import org.objectweb.asm.tree.*; 14 | 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.util.UUID; 18 | 19 | public class ASMUtils { 20 | 21 | 22 | 23 | public static String toDescriptorTypeName(String type) { 24 | switch ( type ) 25 | { 26 | case "byte": 27 | return "B"; 28 | case "char": 29 | return "C"; 30 | case "double": 31 | return "D"; 32 | case "float": 33 | return "F"; 34 | case "int": 35 | return "I"; 36 | case "long": 37 | return "J"; 38 | case "short": 39 | return "S"; 40 | case "boolean": 41 | return "Z"; 42 | case "void": 43 | return "V"; 44 | default: 45 | if ( type.endsWith( "[]" ) ) 46 | { 47 | String s = toDescriptorTypeName(type.substring(0, type.length() - 2)); 48 | return "[" + s.substring(0, s.length()); 49 | } 50 | String clazzType = type.replace( '.', '/' ); 51 | if ( type.startsWith("[") && type.endsWith(";") ) 52 | { 53 | return clazzType; 54 | } 55 | return "L" + clazzType + ";"; 56 | } 57 | } 58 | 59 | public static String getDescriptor(Class returnType, Class... arguments) { 60 | StringBuilder out = new StringBuilder("("); 61 | for (Class argument : arguments) { 62 | out.append(toDescriptorTypeName(argument.getName())); 63 | } 64 | out.append(")").append(toDescriptorTypeName(returnType.getName())); 65 | return out.toString(); 66 | } 67 | 68 | 69 | @SneakyThrows 70 | public static AbstractInsnNode loadVar(Class type, int varNumber) { 71 | String result = "LOAD"; 72 | if (type == byte.class) result = "I" + result; 73 | else if (type == char.class) result = "I" + result; 74 | else if (type == double.class) result = "D" + result; 75 | else if (type == float.class) result = "F" + result; 76 | else if (type == int.class) result = "I" + result; 77 | else if (type == long.class) result = "L" + result; 78 | else if (type == short.class) result = "I" + result; 79 | else if (type == boolean.class) result = "I" + result; 80 | else result = "A" + result; 81 | return new VarInsnNode(((int) Opcode.class.getField(result).get(null)), varNumber); 82 | } 83 | 84 | @SneakyThrows 85 | public static AbstractInsnNode genReturnNode(Class type) { 86 | String result = "RETURN"; 87 | if (type == byte.class) result = "I" + result; 88 | else if (type == char.class) result = "I" + result; 89 | else if (type == double.class) result = "D" + result; 90 | else if (type == float.class) result = "F" + result; 91 | else if (type == int.class) result = "I" + result; 92 | else if (type == long.class) result = "L" + result; 93 | else if (type == short.class) result = "I" + result; 94 | else if (type == boolean.class) result = "I" + result; 95 | else result = "A" + result; 96 | return new InsnNode(((int) Opcode.class.getField(result).get(null))); 97 | } 98 | 99 | @SneakyThrows 100 | public static ClassNode toClassNode(CtClass ctClass) { 101 | ClassNode node = new ClassNode(); 102 | ClassReader reader = new ClassReader(ctClass.toBytecode()); 103 | reader.accept(node, 0); 104 | return node; 105 | } 106 | 107 | @SneakyThrows 108 | public static ClassNode toClassNode(byte[] bytecode) { 109 | ClassNode node = new ClassNode(); 110 | ClassReader reader = new ClassReader(bytecode); 111 | reader.accept(node, 0); 112 | return node; 113 | } 114 | 115 | @SneakyThrows 116 | public static byte[] fromClassNode(ClassNode node) { 117 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 118 | try { 119 | node.accept(writer); 120 | } catch (Exception e) { 121 | e.printStackTrace(); 122 | throw e; 123 | } 124 | 125 | if (MixBukkit.WRITE_TRANSFORMED_CLASS) { 126 | try { 127 | File outFile = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID() + ".class"); 128 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Wrote output class to " + outFile); 129 | FileOutputStream outputStream = new FileOutputStream(outFile); 130 | outputStream.write(writer.toByteArray()); 131 | outputStream.close(); 132 | } catch (Exception e) { 133 | e.printStackTrace(); 134 | } 135 | } 136 | return writer.toByteArray(); 137 | } 138 | 139 | 140 | @SneakyThrows 141 | public static AbstractInsnNode pushInt(int value) { 142 | if (value == -1) { 143 | return new InsnNode(Opcode.ICONST_M1); 144 | } 145 | if (value <= 5 && value >= 0) { 146 | return new InsnNode(value + 3); 147 | } 148 | if (value < 255) { 149 | return new IntInsnNode(Opcode.BIPUSH, value); 150 | } 151 | return new IntInsnNode(Opcode.SIPUSH, value); 152 | } 153 | 154 | public static Class getObjectedType(Class type) { 155 | if (type == byte.class) return Byte.class; 156 | else if (type == char.class) return Character.class; 157 | else if (type == double.class) return Double.class; 158 | else if (type == float.class) return Float.class; 159 | else if (type == int.class) return Integer.class; 160 | else if (type == long.class) return Long.class; 161 | else if (type == short.class) return Short.class; 162 | else if (type == boolean.class) return Boolean.class; 163 | return type; 164 | } 165 | 166 | public static String getObjectedTypeName(Class type) { 167 | if (type == byte.class) return Byte.class.getName().replace(".", "/"); 168 | else if (type == char.class) return Character.class.getName().replace(".", "/"); 169 | else if (type == double.class) return Double.class.getName().replace(".", "/"); 170 | else if (type == float.class) return Float.class.getName().replace(".", "/"); 171 | else if (type == int.class) return Integer.class.getName().replace(".", "/"); 172 | else if (type == long.class) return Long.class.getName().replace(".", "/"); 173 | else if (type == short.class) return Short.class.getName().replace(".", "/"); 174 | else if (type == boolean.class) return Boolean.class.getName().replace(".", "/"); 175 | else return ""; 176 | } 177 | 178 | public static InsnList generateGetClassNode(Class type) { 179 | if (type == byte.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Byte.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 180 | else if (type == char.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Character.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 181 | else if (type == double.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Double.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 182 | else if (type == float.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Float.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 183 | else if (type == int.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Integer.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 184 | else if (type == long.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Long.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 185 | else if (type == short.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Short.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 186 | else if (type == boolean.class) return ASMUtils.asInsnList(new FieldInsnNode(Opcode.GETSTATIC, Boolean.class.getName().replace(".", "/"), "TYPE", "Ljava/lang/Class;")); 187 | return new IShellCodeLoadClassFromPCL(type).generate(); 188 | } 189 | 190 | public static InsnList asInsnList(AbstractInsnNode... nodes) { 191 | InsnList list = new InsnList(); 192 | for (AbstractInsnNode node : nodes) { 193 | list.add(node); 194 | } 195 | return list; 196 | } 197 | 198 | public static InsnList castToObject(int varLocation, Class type) { 199 | InsnList list = new InsnList(); 200 | AbstractInsnNode insnNode = loadVar(type, varLocation); 201 | list.add(insnNode); 202 | String objectedType = getObjectedTypeName(type); 203 | if (objectedType.equals("")) return list; 204 | list.add(new MethodInsnNode(Opcode.INVOKESTATIC, objectedType, "valueOf", getDescriptor(getObjectedType(type), type))); 205 | return list; 206 | } 207 | 208 | @SneakyThrows 209 | public static Class descriptorToClass(String s) { 210 | Class type = null; 211 | switch (s) { 212 | case "Z" : 213 | type = boolean.class; 214 | break; 215 | case "C" : 216 | type = char.class; 217 | break; 218 | case "B" : 219 | type = byte.class; 220 | break; 221 | case "S" : 222 | type = short.class; 223 | break; 224 | case "I" : 225 | type = int.class; 226 | break; 227 | case "J" : 228 | type = long.class; 229 | break; 230 | case "F" : 231 | type = float.class; 232 | break; 233 | case "D" : 234 | type = double.class; 235 | break; 236 | case "V" : 237 | type = void.class; 238 | break; 239 | } 240 | if (s.startsWith("L")) { 241 | type = Class.forName(s.substring(1, s.length()-1)); 242 | } 243 | return type; 244 | } 245 | 246 | public static Class getReturnType(String descriptor) { 247 | String s = descriptor.split("\\)")[1]; 248 | return descriptorToClass(s); 249 | } 250 | 251 | @SneakyThrows 252 | public static InsnList cast(Class type) { 253 | InsnList out = new InsnList(); 254 | Class objectedType = ASMUtils.getObjectedType(type); 255 | if (objectedType != type) { 256 | out.add(new TypeInsnNode(Opcode.CHECKCAST, objectedType.getName().replace(".", "/"))); 257 | out.add(new IShellCodeMethodInvoke(objectedType.getDeclaredMethod(type.getName() + "Value")).generate()); 258 | } else { 259 | out.add(new TypeInsnNode(Opcode.CHECKCAST, objectedType.getName().replace(".", "/"))); 260 | } 261 | return out; 262 | } 263 | 264 | public static int getLatestVarNumber(InsnList list) { 265 | int out = 5; 266 | for (AbstractInsnNode insnNode : list) { 267 | if (insnNode instanceof VarInsnNode) { 268 | int var = ((VarInsnNode) insnNode).var; 269 | out = Math.max(var, out); 270 | } 271 | } 272 | return out; 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/utils/CustomPrinter.java: -------------------------------------------------------------------------------- 1 | // 2 | // Source code recreated from a .class file by IntelliJ IDEA 3 | // (powered by FernFlower decompiler) 4 | // 5 | 6 | package com.dragoncommissions.mixbukkit.utils; 7 | 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | import java.util.ArrayList; 12 | import java.util.Iterator; 13 | import java.util.List; 14 | import org.objectweb.asm.Attribute; 15 | import org.objectweb.asm.ClassReader; 16 | import org.objectweb.asm.ClassVisitor; 17 | import org.objectweb.asm.Handle; 18 | import org.objectweb.asm.Label; 19 | import org.objectweb.asm.TypePath; 20 | import org.objectweb.asm.util.Printer; 21 | import org.objectweb.asm.util.TraceClassVisitor; 22 | 23 | public abstract class CustomPrinter extends Printer { 24 | public static final String[] OPCODES = new String[]{"NOP", "ACONST_NULL", "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "BIPUSH", "SIPUSH", "LDC", "LDC_W", "LDC2_W", "ILOAD", "LLOAD", "FLOAD", "DLOAD", "ALOAD", "ILOAD_0", "ILOAD_1", "ILOAD_2", "ILOAD_3", "LLOAD_0", "LLOAD_1", "LLOAD_2", "LLOAD_3", "FLOAD_0", "FLOAD_1", "FLOAD_2", "FLOAD_3", "DLOAD_0", "DLOAD_1", "DLOAD_2", "DLOAD_3", "ALOAD_0", "ALOAD_1", "ALOAD_2", "ALOAD_3", "IALOAD", "LALOAD", "FALOAD", "DALOAD", "AALOAD", "BALOAD", "CALOAD", "SALOAD", "ISTORE", "LSTORE", "FSTORE", "DSTORE", "ASTORE", "ISTORE_0", "ISTORE_1", "ISTORE_2", "ISTORE_3", "LSTORE_0", "LSTORE_1", "LSTORE_2", "LSTORE_3", "FSTORE_0", "FSTORE_1", "FSTORE_2", "FSTORE_3", "DSTORE_0", "DSTORE_1", "DSTORE_2", "DSTORE_3", "ASTORE_0", "ASTORE_1", "ASTORE_2", "ASTORE_3", "IASTORE", "LASTORE", "FASTORE", "DASTORE", "AASTORE", "BASTORE", "CASTORE", "SASTORE", "POP", "POP2", "DUP", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2", "SWAP", "IADD", "LADD", "FADD", "DADD", "ISUB", "LSUB", "FSUB", "DSUB", "IMUL", "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", "FDIV", "DDIV", "IREM", "LREM", "FREM", "DREM", "INEG", "LNEG", "FNEG", "DNEG", "ISHL", "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", "IAND", "LAND", "IOR", "LOR", "IXOR", "LXOR", "IINC", "I2L", "I2F", "I2D", "L2I", "L2F", "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", "D2F", "I2B", "I2C", "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", "DCMPG", "IFEQ", "IFNE", "IFLT", "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO", "JSR", "RET", "TABLESWITCH", "LOOKUPSWITCH", "IRETURN", "LRETURN", "FRETURN", "DRETURN", "ARETURN", "RETURN", "GETSTATIC", "PUTSTATIC", "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC", "NEW", "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", "ATHROW", "CHECKCAST", "INSTANCEOF", "MONITORENTER", "MONITOREXIT", "WIDE", "MULTIANEWARRAY", "IFNULL", "IFNONNULL"}; 25 | public static final String[] TYPES = new String[]{"", "", "", "", "T_BOOLEAN", "T_CHAR", "T_FLOAT", "T_DOUBLE", "T_BYTE", "T_SHORT", "T_INT", "T_LONG"}; 26 | public static final String[] HANDLE_TAG = new String[]{"", "H_GETFIELD", "H_GETSTATIC", "H_PUTFIELD", "H_PUTSTATIC", "H_INVOKEVIRTUAL", "H_INVOKESTATIC", "H_INVOKESPECIAL", "H_NEWINVOKESPECIAL", "H_INVOKEINTERFACE"}; 27 | private static final String UNSUPPORTED_OPERATION = "Must be overridden"; 28 | protected final int api; 29 | protected final StringBuilder stringBuilder; 30 | public final List text; 31 | 32 | protected CustomPrinter(int api) { 33 | super(api); 34 | this.api = api; 35 | this.stringBuilder = new StringBuilder(); 36 | this.text = new ArrayList(); 37 | } 38 | 39 | public abstract void visit(int var1, int var2, String var3, String var4, String var5, String[] var6); 40 | 41 | public abstract void visitSource(String var1, String var2); 42 | 43 | public CustomPrinter visitModule(String name, int access, String version) { 44 | throw new UnsupportedOperationException("Must be overridden"); 45 | } 46 | 47 | public void visitNestHost(String nestHost) { 48 | throw new UnsupportedOperationException("Must be overridden"); 49 | } 50 | 51 | public abstract void visitOuterClass(String var1, String var2, String var3); 52 | 53 | public abstract CustomPrinter visitClassAnnotation(String var1, boolean var2); 54 | 55 | public CustomPrinter visitClassTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 56 | throw new UnsupportedOperationException("Must be overridden"); 57 | } 58 | 59 | public abstract void visitClassAttribute(Attribute var1); 60 | 61 | public void visitNestMember(String nestMember) { 62 | throw new UnsupportedOperationException("Must be overridden"); 63 | } 64 | 65 | public void visitPermittedSubclass(String permittedSubclass) { 66 | throw new UnsupportedOperationException("Must be overridden"); 67 | } 68 | 69 | public abstract void visitInnerClass(String var1, String var2, String var3, int var4); 70 | 71 | public CustomPrinter visitRecordComponent(String name, String descriptor, String signature) { 72 | throw new UnsupportedOperationException("Must be overridden"); 73 | } 74 | 75 | public abstract CustomPrinter visitField(int var1, String var2, String var3, String var4, Object var5); 76 | 77 | public abstract CustomPrinter visitMethod(int var1, String var2, String var3, String var4, String[] var5); 78 | 79 | public abstract void visitClassEnd(); 80 | 81 | public void visitMainClass(String mainClass) { 82 | throw new UnsupportedOperationException("Must be overridden"); 83 | } 84 | 85 | public void visitPackage(String packaze) { 86 | throw new UnsupportedOperationException("Must be overridden"); 87 | } 88 | 89 | public void visitRequire(String module, int access, String version) { 90 | throw new UnsupportedOperationException("Must be overridden"); 91 | } 92 | 93 | public void visitExport(String packaze, int access, String... modules) { 94 | throw new UnsupportedOperationException("Must be overridden"); 95 | } 96 | 97 | public void visitOpen(String packaze, int access, String... modules) { 98 | throw new UnsupportedOperationException("Must be overridden"); 99 | } 100 | 101 | public void visitUse(String service) { 102 | throw new UnsupportedOperationException("Must be overridden"); 103 | } 104 | 105 | public void visitProvide(String service, String... providers) { 106 | throw new UnsupportedOperationException("Must be overridden"); 107 | } 108 | 109 | public void visitModuleEnd() { 110 | throw new UnsupportedOperationException("Must be overridden"); 111 | } 112 | 113 | public abstract void visit(String var1, Object var2); 114 | 115 | public abstract void visitEnum(String var1, String var2, String var3); 116 | 117 | public abstract CustomPrinter visitAnnotation(String var1, String var2); 118 | 119 | public abstract CustomPrinter visitArray(String var1); 120 | 121 | public abstract void visitAnnotationEnd(); 122 | 123 | public CustomPrinter visitRecordComponentAnnotation(String descriptor, boolean visible) { 124 | throw new UnsupportedOperationException("Must be overridden"); 125 | } 126 | 127 | public CustomPrinter visitRecordComponentTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 128 | throw new UnsupportedOperationException("Must be overridden"); 129 | } 130 | 131 | public void visitRecordComponentAttribute(Attribute attribute) { 132 | throw new UnsupportedOperationException("Must be overridden"); 133 | } 134 | 135 | public void visitRecordComponentEnd() { 136 | throw new UnsupportedOperationException("Must be overridden"); 137 | } 138 | 139 | public abstract CustomPrinter visitFieldAnnotation(String var1, boolean var2); 140 | 141 | public CustomPrinter visitFieldTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 142 | throw new UnsupportedOperationException("Must be overridden"); 143 | } 144 | 145 | public abstract void visitFieldAttribute(Attribute var1); 146 | 147 | public abstract void visitFieldEnd(); 148 | 149 | public void visitParameter(String name, int access) { 150 | throw new UnsupportedOperationException("Must be overridden"); 151 | } 152 | 153 | public abstract CustomPrinter visitAnnotationDefault(); 154 | 155 | public abstract CustomPrinter visitMethodAnnotation(String var1, boolean var2); 156 | 157 | public CustomPrinter visitMethodTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 158 | throw new UnsupportedOperationException("Must be overridden"); 159 | } 160 | 161 | public CustomPrinter visitAnnotableParameterCount(int parameterCount, boolean visible) { 162 | throw new UnsupportedOperationException("Must be overridden"); 163 | } 164 | 165 | public abstract CustomPrinter visitParameterAnnotation(int var1, String var2, boolean var3); 166 | 167 | public abstract void visitMethodAttribute(Attribute var1); 168 | 169 | public abstract void visitCode(); 170 | 171 | public abstract void visitFrame(int var1, int var2, Object[] var3, int var4, Object[] var5); 172 | 173 | public abstract void visitInsn(int var1); 174 | 175 | public abstract void visitIntInsn(int var1, int var2); 176 | 177 | public abstract void visitVarInsn(int var1, int var2); 178 | 179 | public abstract void visitTypeInsn(int var1, String var2); 180 | 181 | public abstract void visitFieldInsn(int var1, String var2, String var3, String var4); 182 | 183 | /** @deprecated */ 184 | @Deprecated 185 | public void visitMethodInsn(int opcode, String owner, String name, String descriptor) { 186 | this.visitMethodInsn(opcode, owner, name, descriptor, opcode == 185); 187 | } 188 | 189 | public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { 190 | throw new UnsupportedOperationException("Must be overridden"); 191 | } 192 | 193 | public abstract void visitInvokeDynamicInsn(String var1, String var2, Handle var3, Object... var4); 194 | 195 | public abstract void visitJumpInsn(int var1, Label var2); 196 | 197 | public abstract void visitLabel(Label var1); 198 | 199 | public abstract void visitLdcInsn(Object var1); 200 | 201 | public abstract void visitIincInsn(int var1, int var2); 202 | 203 | public abstract void visitTableSwitchInsn(int var1, int var2, Label var3, Label... var4); 204 | 205 | public abstract void visitLookupSwitchInsn(Label var1, int[] var2, Label[] var3); 206 | 207 | public abstract void visitMultiANewArrayInsn(String var1, int var2); 208 | 209 | public CustomPrinter visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 210 | throw new UnsupportedOperationException("Must be overridden"); 211 | } 212 | 213 | public abstract void visitTryCatchBlock(Label var1, Label var2, Label var3, String var4); 214 | 215 | public CustomPrinter visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { 216 | throw new UnsupportedOperationException("Must be overridden"); 217 | } 218 | 219 | public abstract void visitLocalVariable(String var1, String var2, String var3, Label var4, Label var5, int var6); 220 | 221 | public CustomPrinter visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) { 222 | throw new UnsupportedOperationException("Must be overridden"); 223 | } 224 | 225 | public abstract void visitLineNumber(int var1, Label var2); 226 | 227 | public abstract void visitMaxs(int var1, int var2); 228 | 229 | public abstract void visitMethodEnd(); 230 | 231 | public List getText() { 232 | return this.text; 233 | } 234 | 235 | public void print(PrintWriter printWriter) { 236 | printList(printWriter, this.text); 237 | } 238 | 239 | static void printList(PrintWriter printWriter, List list) { 240 | Iterator var2 = list.iterator(); 241 | 242 | while(var2.hasNext()) { 243 | Object o = var2.next(); 244 | if (o instanceof List) { 245 | printList(printWriter, (List)o); 246 | } else { 247 | printWriter.print(o.toString()); 248 | } 249 | } 250 | 251 | } 252 | 253 | public static void appendString(StringBuilder stringBuilder, String string) { 254 | stringBuilder.append('"'); 255 | 256 | for(int i = 0; i < string.length(); ++i) { 257 | char c = string.charAt(i); 258 | if (c == '\n') { 259 | stringBuilder.append("\\n"); 260 | } else if (c == '\r') { 261 | stringBuilder.append("\\r"); 262 | } else if (c == '\\') { 263 | stringBuilder.append("\\\\"); 264 | } else if (c == '"') { 265 | stringBuilder.append("\\\""); 266 | } else if (c >= ' ' && c <= 127) { 267 | stringBuilder.append(c); 268 | } else { 269 | stringBuilder.append("\\u"); 270 | if (c < 16) { 271 | stringBuilder.append("000"); 272 | } else if (c < 256) { 273 | stringBuilder.append("00"); 274 | } else if (c < 4096) { 275 | stringBuilder.append('0'); 276 | } 277 | 278 | stringBuilder.append(Integer.toString(c, 16)); 279 | } 280 | } 281 | 282 | stringBuilder.append('"'); 283 | } 284 | 285 | static void main(String[] args, String usage, CustomPrinter printer, PrintWriter output, PrintWriter logger) throws IOException { 286 | if (args.length >= 1 && args.length <= 2 && (!args[0].equals("-debug") && !args[0].equals("-nodebug") || args.length == 2)) { 287 | TraceClassVisitor traceClassVisitor = new TraceClassVisitor((ClassVisitor)null, printer, output); 288 | String className; 289 | byte parsingOptions; 290 | if (args[0].equals("-nodebug")) { 291 | className = args[1]; 292 | parsingOptions = 2; 293 | } else { 294 | className = args[0]; 295 | parsingOptions = 0; 296 | } 297 | 298 | if (!className.endsWith(".class") && className.indexOf(92) == -1 && className.indexOf(47) == -1) { 299 | (new ClassReader(className)).accept(traceClassVisitor, parsingOptions); 300 | } else { 301 | FileInputStream inputStream = new FileInputStream(className); 302 | 303 | try { 304 | (new ClassReader(inputStream)).accept(traceClassVisitor, parsingOptions); 305 | } catch (Throwable var12) { 306 | try { 307 | inputStream.close(); 308 | } catch (Throwable var11) { 309 | } 310 | 311 | throw var12; 312 | } 313 | 314 | inputStream.close(); 315 | } 316 | 317 | } else { 318 | logger.println(usage); 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/utils/CustomTextifier.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils; 2 | 3 | import org.objectweb.asm.*; 4 | import org.objectweb.asm.signature.SignatureReader; 5 | import org.objectweb.asm.util.TextifierSupport; 6 | import org.objectweb.asm.util.TraceSignatureVisitor; 7 | 8 | import java.util.*; 9 | 10 | public class CustomTextifier extends MethodVisitor { 11 | public static final String[] OPCODES = new String[]{"NOP", "ACONST_NULL", "ICONST_M1", "ICONST_0", "ICONST_1", "ICONST_2", "ICONST_3", "ICONST_4", "ICONST_5", "LCONST_0", "LCONST_1", "FCONST_0", "FCONST_1", "FCONST_2", "DCONST_0", "DCONST_1", "BIPUSH", "SIPUSH", "LDC", "LDC_W", "LDC2_W", "ILOAD", "LLOAD", "FLOAD", "DLOAD", "ALOAD", "ILOAD_0", "ILOAD_1", "ILOAD_2", "ILOAD_3", "LLOAD_0", "LLOAD_1", "LLOAD_2", "LLOAD_3", "FLOAD_0", "FLOAD_1", "FLOAD_2", "FLOAD_3", "DLOAD_0", "DLOAD_1", "DLOAD_2", "DLOAD_3", "ALOAD_0", "ALOAD_1", "ALOAD_2", "ALOAD_3", "IALOAD", "LALOAD", "FALOAD", "DALOAD", "AALOAD", "BALOAD", "CALOAD", "SALOAD", "ISTORE", "LSTORE", "FSTORE", "DSTORE", "ASTORE", "ISTORE_0", "ISTORE_1", "ISTORE_2", "ISTORE_3", "LSTORE_0", "LSTORE_1", "LSTORE_2", "LSTORE_3", "FSTORE_0", "FSTORE_1", "FSTORE_2", "FSTORE_3", "DSTORE_0", "DSTORE_1", "DSTORE_2", "DSTORE_3", "ASTORE_0", "ASTORE_1", "ASTORE_2", "ASTORE_3", "IASTORE", "LASTORE", "FASTORE", "DASTORE", "AASTORE", "BASTORE", "CASTORE", "SASTORE", "POP", "POP2", "DUP", "DUP_X1", "DUP_X2", "DUP2", "DUP2_X1", "DUP2_X2", "SWAP", "IADD", "LADD", "FADD", "DADD", "ISUB", "LSUB", "FSUB", "DSUB", "IMUL", "LMUL", "FMUL", "DMUL", "IDIV", "LDIV", "FDIV", "DDIV", "IREM", "LREM", "FREM", "DREM", "INEG", "LNEG", "FNEG", "DNEG", "ISHL", "LSHL", "ISHR", "LSHR", "IUSHR", "LUSHR", "IAND", "LAND", "IOR", "LOR", "IXOR", "LXOR", "IINC", "I2L", "I2F", "I2D", "L2I", "L2F", "L2D", "F2I", "F2L", "F2D", "D2I", "D2L", "D2F", "I2B", "I2C", "I2S", "LCMP", "FCMPL", "FCMPG", "DCMPL", "DCMPG", "IFEQ", "IFNE", "IFLT", "IFGE", "IFGT", "IFLE", "IF_ICMPEQ", "IF_ICMPNE", "IF_ICMPLT", "IF_ICMPGE", "IF_ICMPGT", "IF_ICMPLE", "IF_ACMPEQ", "IF_ACMPNE", "GOTO", "JSR", "RET", "TABLESWITCH", "LOOKUPSWITCH", "IRETURN", "LRETURN", "FRETURN", "DRETURN", "ARETURN", "RETURN", "GETSTATIC", "PUTSTATIC", "GETFIELD", "PUTFIELD", "INVOKEVIRTUAL", "INVOKESPECIAL", "INVOKESTATIC", "INVOKEINTERFACE", "INVOKEDYNAMIC", "NEW", "NEWARRAY", "ANEWARRAY", "ARRAYLENGTH", "ATHROW", "CHECKCAST", "INSTANCEOF", "MONITORENTER", "MONITOREXIT", "WIDE", "MULTIANEWARRAY", "IFNULL", "IFNONNULL"}; 12 | public static final String[] TYPES = new String[]{"", "", "", "", "T_BOOLEAN", "T_CHAR", "T_FLOAT", "T_DOUBLE", "T_BYTE", "T_SHORT", "T_INT", "T_LONG"}; 13 | public static final String[] HANDLE_TAG = new String[]{"", "H_GETFIELD", "H_GETSTATIC", "H_PUTFIELD", "H_PUTSTATIC", "H_INVOKEVIRTUAL", "H_INVOKESTATIC", "H_INVOKESPECIAL", "H_NEWINVOKESPECIAL", "H_INVOKEINTERFACE"}; 14 | private static final String UNSUPPORTED_OPERATION = "Must be overridden"; 15 | private static final String USAGE = "Prints a disassembled view of the given class.\nUsage: Textifier [-nodebug] "; 16 | public static final int INTERNAL_NAME = 0; 17 | public static final int FIELD_DESCRIPTOR = 1; 18 | public static final int FIELD_SIGNATURE = 2; 19 | public static final int METHOD_DESCRIPTOR = 3; 20 | public static final int METHOD_SIGNATURE = 4; 21 | public static final int CLASS_SIGNATURE = 5; 22 | public static final int HANDLE_DESCRIPTOR = 9; 23 | private static final String CLASS_SUFFIX = ".class"; 24 | private static final String DEPRECATED = "// DEPRECATED\n"; 25 | private static final String RECORD = "// RECORD\n"; 26 | private static final String INVISIBLE = " // invisible\n"; 27 | private static final List FRAME_TYPES = Collections.unmodifiableList(Arrays.asList("T", "I", "F", "D", "J", "N", "U")); 28 | protected String tab; 29 | protected String tab2; 30 | protected String tab3; 31 | protected String ltab; 32 | protected Map labelNames; 33 | private int access; 34 | private int numAnnotationValues; 35 | public final List text = new ArrayList<>(); 36 | protected final StringBuilder stringBuilder = new StringBuilder(); 37 | 38 | public CustomTextifier() { 39 | this(589824); 40 | if (this.getClass() != CustomTextifier.class) { 41 | throw new IllegalStateException(); 42 | } 43 | } 44 | 45 | protected CustomTextifier(int api) { 46 | super(api); 47 | this.tab = "\t "; 48 | this.tab2 = "\t \t "; 49 | this.tab3 = "\t \t \t "; 50 | this.ltab = "\t\t "; 51 | } 52 | 53 | 54 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 55 | if ((access & '耀') == 0) { 56 | this.access = access; 57 | int majorVersion = version & '\uffff'; 58 | int minorVersion = version >>> 16; 59 | this.stringBuilder.setLength(0); 60 | this.stringBuilder.append("// class version ").append(majorVersion).append('.').append(minorVersion).append(" (").append(version).append(")\n"); 61 | if ((access & 131072) != 0) { 62 | this.stringBuilder.append("// DEPRECATED\n"); 63 | } 64 | 65 | if ((access & 65536) != 0) { 66 | this.stringBuilder.append("// RECORD\n"); 67 | } 68 | 69 | this.appendRawAccess(access); 70 | this.appendDescriptor(5, signature); 71 | if (signature != null) { 72 | this.appendJavaDeclaration(name, signature); 73 | } 74 | 75 | this.appendAccess(access & -32801); 76 | if ((access & 8192) != 0) { 77 | this.stringBuilder.append("@interface "); 78 | } else if ((access & 512) != 0) { 79 | this.stringBuilder.append("interface "); 80 | } else if ((access & 16384) == 0) { 81 | this.stringBuilder.append("class "); 82 | } 83 | 84 | this.appendDescriptor(0, name); 85 | if (superName != null && !"java/lang/Object".equals(superName)) { 86 | this.stringBuilder.append(" extends "); 87 | this.appendDescriptor(0, superName); 88 | } 89 | 90 | if (interfaces != null && interfaces.length > 0) { 91 | this.stringBuilder.append(" implements "); 92 | 93 | for(int i = 0; i < interfaces.length; ++i) { 94 | this.appendDescriptor(0, interfaces[i]); 95 | if (i != interfaces.length - 1) { 96 | this.stringBuilder.append(' '); 97 | } 98 | } 99 | } 100 | 101 | this.stringBuilder.append(" {\n\n"); 102 | this.text.add(this.stringBuilder.toString()); 103 | } 104 | } 105 | 106 | public void visitSource(String file, String debug) { 107 | this.stringBuilder.setLength(0); 108 | if (file != null) { 109 | this.stringBuilder.append(this.tab).append("// compiled from: ").append(file); 110 | } 111 | 112 | if (debug != null) { 113 | this.stringBuilder.append(this.tab).append("// debug info: ").append(debug); 114 | } 115 | 116 | if (this.stringBuilder.length() > 0) { 117 | this.text.add(this.stringBuilder.toString()); 118 | } 119 | 120 | } 121 | 122 | public CustomTextifier visitModule(String name, int access, String version) { 123 | this.stringBuilder.setLength(0); 124 | if ((access & 32) != 0) { 125 | this.stringBuilder.append("open "); 126 | } 127 | 128 | this.stringBuilder.append("module ").append(name).append(" { ").append(version == null ? "" : "// " + version).append("\n\n"); 129 | this.text.add(this.stringBuilder.toString()); 130 | return this.addNewTextifier((String)null); 131 | } 132 | 133 | public void visitNestHost(String nestHost) { 134 | this.stringBuilder.setLength(0); 135 | this.stringBuilder.append(this.tab).append("NESTHOST "); 136 | this.appendDescriptor(0, nestHost); 137 | 138 | this.text.add(this.stringBuilder.toString()); 139 | } 140 | 141 | public void visitOuterClass(String owner, String name, String descriptor) { 142 | this.stringBuilder.setLength(0); 143 | this.stringBuilder.append(this.tab).append("OUTERCLASS "); 144 | this.appendDescriptor(0, owner); 145 | this.stringBuilder.append(' '); 146 | if (name != null) { 147 | this.stringBuilder.append(name).append(' '); 148 | } 149 | 150 | this.appendDescriptor(3, descriptor); 151 | 152 | this.text.add(this.stringBuilder.toString()); 153 | } 154 | 155 | 156 | public void visitClassAttribute(Attribute attribute) { 157 | this.text.add("\n"); 158 | this.visitAttribute(attribute); 159 | } 160 | 161 | public void visitNestMember(String nestMember) { 162 | this.stringBuilder.setLength(0); 163 | this.stringBuilder.append(this.tab).append("NESTMEMBER "); 164 | this.appendDescriptor(0, nestMember); 165 | 166 | this.text.add(this.stringBuilder.toString()); 167 | } 168 | 169 | public void visitPermittedSubclass(String permittedSubclass) { 170 | this.stringBuilder.setLength(0); 171 | this.stringBuilder.append(this.tab).append("PERMITTEDSUBCLASS "); 172 | this.appendDescriptor(0, permittedSubclass); 173 | 174 | this.text.add(this.stringBuilder.toString()); 175 | } 176 | 177 | public void visitInnerClass(String name, String outerName, String innerName, int access) { 178 | this.stringBuilder.setLength(0); 179 | this.stringBuilder.append(this.tab); 180 | this.appendRawAccess(access & -33); 181 | this.stringBuilder.append(this.tab); 182 | this.appendAccess(access); 183 | this.stringBuilder.append("INNERCLASS "); 184 | this.appendDescriptor(0, name); 185 | this.stringBuilder.append(' '); 186 | this.appendDescriptor(0, outerName); 187 | this.stringBuilder.append(' '); 188 | this.appendDescriptor(0, innerName); 189 | 190 | this.text.add(this.stringBuilder.toString()); 191 | } 192 | 193 | public CustomTextifier visitRecordComponent(String name, String descriptor, String signature) { 194 | this.stringBuilder.setLength(0); 195 | this.stringBuilder.append(this.tab).append("RECORDCOMPONENT "); 196 | if (signature != null) { 197 | this.stringBuilder.append(this.tab); 198 | this.appendDescriptor(2, signature); 199 | this.stringBuilder.append(this.tab); 200 | this.appendJavaDeclaration(name, signature); 201 | } 202 | 203 | this.stringBuilder.append(this.tab); 204 | this.appendDescriptor(1, descriptor); 205 | this.stringBuilder.append(' ').append(name); 206 | 207 | this.text.add(this.stringBuilder.toString()); 208 | return this.addNewTextifier((String)null); 209 | } 210 | 211 | public CustomTextifier visitField(int access, String name, String descriptor, String signature, Object value) { 212 | this.stringBuilder.setLength(0); 213 | 214 | if ((access & 131072) != 0) { 215 | this.stringBuilder.append(this.tab).append("// DEPRECATED\n"); 216 | } 217 | 218 | this.stringBuilder.append(this.tab); 219 | this.appendRawAccess(access); 220 | if (signature != null) { 221 | this.stringBuilder.append(this.tab); 222 | this.appendDescriptor(2, signature); 223 | this.stringBuilder.append(this.tab); 224 | this.appendJavaDeclaration(name, signature); 225 | } 226 | 227 | this.stringBuilder.append(this.tab); 228 | this.appendAccess(access); 229 | this.appendDescriptor(1, descriptor); 230 | this.stringBuilder.append(' ').append(name); 231 | if (value != null) { 232 | this.stringBuilder.append(" = "); 233 | if (value instanceof String) { 234 | this.stringBuilder.append('"').append(value).append('"'); 235 | } else { 236 | this.stringBuilder.append(value); 237 | } 238 | } 239 | 240 | 241 | this.text.add(this.stringBuilder.toString()); 242 | return this.addNewTextifier((String)null); 243 | } 244 | 245 | public CustomTextifier visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 246 | this.stringBuilder.setLength(0); 247 | 248 | if ((access & 131072) != 0) { 249 | this.stringBuilder.append(this.tab).append("// DEPRECATED\n"); 250 | } 251 | 252 | this.stringBuilder.append(this.tab); 253 | this.appendRawAccess(access); 254 | if (signature != null) { 255 | this.stringBuilder.append(this.tab); 256 | this.appendDescriptor(4, signature); 257 | this.stringBuilder.append(this.tab); 258 | this.appendJavaDeclaration(name, signature); 259 | } 260 | 261 | this.stringBuilder.append(this.tab); 262 | this.appendAccess(access & -193); 263 | if ((access & 256) != 0) { 264 | this.stringBuilder.append("native "); 265 | } 266 | 267 | if ((access & 128) != 0) { 268 | this.stringBuilder.append("varargs "); 269 | } 270 | 271 | if ((access & 64) != 0) { 272 | this.stringBuilder.append("bridge "); 273 | } 274 | 275 | if ((this.access & 512) != 0 && (access & 1032) == 0) { 276 | this.stringBuilder.append("default "); 277 | } 278 | 279 | this.stringBuilder.append(name); 280 | this.appendDescriptor(3, descriptor); 281 | if (exceptions != null && exceptions.length > 0) { 282 | this.stringBuilder.append(" throws "); 283 | String[] var6 = exceptions; 284 | int var7 = exceptions.length; 285 | 286 | for(int var8 = 0; var8 < var7; ++var8) { 287 | String exception = var6[var8]; 288 | this.appendDescriptor(0, exception); 289 | this.stringBuilder.append(' '); 290 | } 291 | } 292 | 293 | 294 | this.text.add(this.stringBuilder.toString()); 295 | return this.addNewTextifier((String)null); 296 | } 297 | 298 | public void visitClassEnd() { 299 | this.text.add("}\n"); 300 | } 301 | 302 | public void visitMainClass(String mainClass) { 303 | this.stringBuilder.setLength(0); 304 | this.stringBuilder.append(" // main class ").append(mainClass); 305 | this.text.add(this.stringBuilder.toString()); 306 | } 307 | 308 | public void visitPackage(String packaze) { 309 | this.stringBuilder.setLength(0); 310 | this.stringBuilder.append(" // package ").append(packaze); 311 | this.text.add(this.stringBuilder.toString()); 312 | } 313 | 314 | public void visitRequire(String require, int access, String version) { 315 | this.stringBuilder.setLength(0); 316 | this.stringBuilder.append(this.tab).append("requires "); 317 | if ((access & 32) != 0) { 318 | this.stringBuilder.append("transitive "); 319 | } 320 | 321 | if ((access & 64) != 0) { 322 | this.stringBuilder.append("static "); 323 | } 324 | 325 | this.stringBuilder.append(require).append(';'); 326 | this.appendRawAccess(access); 327 | if (version != null) { 328 | this.stringBuilder.append(" // version ").append(version); 329 | } 330 | 331 | this.text.add(this.stringBuilder.toString()); 332 | } 333 | 334 | public void visitExport(String packaze, int access, String... modules) { 335 | this.visitExportOrOpen("exports ", packaze, access, modules); 336 | } 337 | 338 | public void visitOpen(String packaze, int access, String... modules) { 339 | this.visitExportOrOpen("opens ", packaze, access, modules); 340 | } 341 | 342 | private void visitExportOrOpen(String method, String packaze, int access, String... modules) { 343 | this.stringBuilder.setLength(0); 344 | this.stringBuilder.append(this.tab).append(method); 345 | this.stringBuilder.append(packaze); 346 | if (modules != null && modules.length > 0) { 347 | this.stringBuilder.append(" to"); 348 | } else { 349 | this.stringBuilder.append(';'); 350 | } 351 | 352 | this.appendRawAccess(access); 353 | if (modules != null && modules.length > 0) { 354 | for(int i = 0; i < modules.length; ++i) { 355 | this.stringBuilder.append(this.tab2).append(modules[i]); 356 | this.stringBuilder.append(i != modules.length - 1 ? ",\n" : ";\n"); 357 | } 358 | } 359 | 360 | this.text.add(this.stringBuilder.toString()); 361 | } 362 | 363 | public void visitUse(String use) { 364 | this.stringBuilder.setLength(0); 365 | this.stringBuilder.append(this.tab).append("uses "); 366 | this.appendDescriptor(0, use); 367 | this.stringBuilder.append(";\n"); 368 | this.text.add(this.stringBuilder.toString()); 369 | } 370 | 371 | public void visitProvide(String provide, String... providers) { 372 | this.stringBuilder.setLength(0); 373 | this.stringBuilder.append(this.tab).append("provides "); 374 | this.appendDescriptor(0, provide); 375 | this.stringBuilder.append(" with\n"); 376 | 377 | for(int i = 0; i < providers.length; ++i) { 378 | this.stringBuilder.append(this.tab2); 379 | this.appendDescriptor(0, providers[i]); 380 | this.stringBuilder.append(i != providers.length - 1 ? ",\n" : ";\n"); 381 | } 382 | 383 | this.text.add(this.stringBuilder.toString()); 384 | } 385 | 386 | public void visitModuleEnd() { 387 | } 388 | 389 | public void visit(String name, Object value) { 390 | this.visitAnnotationValue(name); 391 | if (value instanceof String) { 392 | this.visitString((String)value); 393 | } else if (value instanceof Type) { 394 | this.visitType((Type)value); 395 | } else if (value instanceof Byte) { 396 | this.visitByte((Byte)value); 397 | } else if (value instanceof Boolean) { 398 | this.visitBoolean((Boolean)value); 399 | } else if (value instanceof Short) { 400 | this.visitShort((Short)value); 401 | } else if (value instanceof Character) { 402 | this.visitChar((Character)value); 403 | } else if (value instanceof Integer) { 404 | this.visitInt((Integer)value); 405 | } else if (value instanceof Float) { 406 | this.visitFloat((Float)value); 407 | } else if (value instanceof Long) { 408 | this.visitLong((Long)value); 409 | } else if (value instanceof Double) { 410 | this.visitDouble((Double)value); 411 | } else if (value.getClass().isArray()) { 412 | this.stringBuilder.append('{'); 413 | int i; 414 | if (value instanceof byte[]) { 415 | byte[] byteArray = (byte[])value; 416 | 417 | for(i = 0; i < byteArray.length; ++i) { 418 | this.maybeAppendComma(i); 419 | this.visitByte(byteArray[i]); 420 | } 421 | } else if (value instanceof boolean[]) { 422 | boolean[] booleanArray = (boolean[])value; 423 | 424 | for(i = 0; i < booleanArray.length; ++i) { 425 | this.maybeAppendComma(i); 426 | this.visitBoolean(booleanArray[i]); 427 | } 428 | } else if (value instanceof short[]) { 429 | short[] shortArray = (short[])value; 430 | 431 | for(i = 0; i < shortArray.length; ++i) { 432 | this.maybeAppendComma(i); 433 | this.visitShort(shortArray[i]); 434 | } 435 | } else if (value instanceof char[]) { 436 | char[] charArray = (char[])value; 437 | 438 | for(i = 0; i < charArray.length; ++i) { 439 | this.maybeAppendComma(i); 440 | this.visitChar(charArray[i]); 441 | } 442 | } else if (value instanceof int[]) { 443 | int[] intArray = (int[])value; 444 | 445 | for(i = 0; i < intArray.length; ++i) { 446 | this.maybeAppendComma(i); 447 | this.visitInt(intArray[i]); 448 | } 449 | } else if (value instanceof long[]) { 450 | long[] longArray = (long[])value; 451 | 452 | for(i = 0; i < longArray.length; ++i) { 453 | this.maybeAppendComma(i); 454 | this.visitLong(longArray[i]); 455 | } 456 | } else if (value instanceof float[]) { 457 | float[] floatArray = (float[])value; 458 | 459 | for(i = 0; i < floatArray.length; ++i) { 460 | this.maybeAppendComma(i); 461 | this.visitFloat(floatArray[i]); 462 | } 463 | } else if (value instanceof double[]) { 464 | double[] doubleArray = (double[])value; 465 | 466 | for(i = 0; i < doubleArray.length; ++i) { 467 | this.maybeAppendComma(i); 468 | this.visitDouble(doubleArray[i]); 469 | } 470 | } 471 | 472 | this.stringBuilder.append('}'); 473 | } 474 | 475 | this.text.add(this.stringBuilder.toString()); 476 | } 477 | 478 | private void visitInt(int value) { 479 | this.stringBuilder.append(value); 480 | } 481 | 482 | private void visitLong(long value) { 483 | this.stringBuilder.append(value).append('L'); 484 | } 485 | 486 | private void visitFloat(float value) { 487 | this.stringBuilder.append(value).append('F'); 488 | } 489 | 490 | private void visitDouble(double value) { 491 | this.stringBuilder.append(value).append('D'); 492 | } 493 | 494 | private void visitChar(char value) { 495 | this.stringBuilder.append("(char)").append(value); 496 | } 497 | 498 | private void visitShort(short value) { 499 | this.stringBuilder.append("(short)").append(value); 500 | } 501 | 502 | private void visitByte(byte value) { 503 | this.stringBuilder.append("(byte)").append(value); 504 | } 505 | 506 | private void visitBoolean(boolean value) { 507 | this.stringBuilder.append(value); 508 | } 509 | 510 | private void visitString(String value) { 511 | appendString(this.stringBuilder, value); 512 | } 513 | 514 | public static void appendString(StringBuilder stringBuilder, String string) { 515 | stringBuilder.append('"'); 516 | 517 | for(int i = 0; i < string.length(); ++i) { 518 | char c = string.charAt(i); 519 | if (c == '\n') { 520 | stringBuilder.append("\\n"); 521 | } else if (c == '\r') { 522 | stringBuilder.append("\\r"); 523 | } else if (c == '\\') { 524 | stringBuilder.append("\\\\"); 525 | } else if (c == '"') { 526 | stringBuilder.append("\\\""); 527 | } else if (c >= ' ' && c <= 127) { 528 | stringBuilder.append(c); 529 | } else { 530 | stringBuilder.append("\\u"); 531 | if (c < 16) { 532 | stringBuilder.append("000"); 533 | } else if (c < 256) { 534 | stringBuilder.append("00"); 535 | } else if (c < 4096) { 536 | stringBuilder.append('0'); 537 | } 538 | 539 | stringBuilder.append(Integer.toString(c, 16)); 540 | } 541 | } 542 | 543 | stringBuilder.append('"'); 544 | } 545 | 546 | private void visitType(Type value) { 547 | this.stringBuilder.append(value.getClassName()).append(".class"); 548 | } 549 | 550 | public void visitEnum(String name, String descriptor, String value) { 551 | this.visitAnnotationValue(name); 552 | this.appendDescriptor(1, descriptor); 553 | this.stringBuilder.append('.').append(value); 554 | this.text.add(this.stringBuilder.toString()); 555 | } 556 | 557 | public CustomTextifier visitAnnotation(String name, String descriptor) { 558 | this.visitAnnotationValue(name); 559 | this.stringBuilder.append('@'); 560 | this.appendDescriptor(1, descriptor); 561 | this.stringBuilder.append('('); 562 | this.text.add(this.stringBuilder.toString()); 563 | return this.addNewTextifier(")"); 564 | } 565 | 566 | public CustomTextifier visitArray(String name) { 567 | this.visitAnnotationValue(name); 568 | this.stringBuilder.append('{'); 569 | this.text.add(this.stringBuilder.toString()); 570 | return this.addNewTextifier("}"); 571 | } 572 | 573 | public void visitAnnotationEnd() { 574 | } 575 | 576 | private void visitAnnotationValue(String name) { 577 | this.stringBuilder.setLength(0); 578 | this.maybeAppendComma(this.numAnnotationValues++); 579 | if (name != null) { 580 | this.stringBuilder.append(name).append('='); 581 | } 582 | 583 | } 584 | 585 | 586 | public void visitRecordComponentAttribute(Attribute attribute) { 587 | this.visitAttribute(attribute); 588 | } 589 | 590 | public void visitRecordComponentEnd() { 591 | } 592 | 593 | 594 | public void visitFieldAttribute(Attribute attribute) { 595 | this.visitAttribute(attribute); 596 | } 597 | 598 | public void visitFieldEnd() { 599 | } 600 | 601 | public void visitParameter(String name, int access) { 602 | this.stringBuilder.setLength(0); 603 | this.stringBuilder.append(this.tab2).append("// parameter "); 604 | this.appendAccess(access); 605 | this.stringBuilder.append(' ').append(name == null ? "" : name); 606 | this.text.add(this.stringBuilder.toString()); 607 | } 608 | 609 | 610 | public void visitMethodAttribute(Attribute attribute) { 611 | this.visitAttribute(attribute); 612 | } 613 | 614 | public void visitCode() { 615 | } 616 | 617 | public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { 618 | rip++; 619 | this.stringBuilder.setLength(0); 620 | this.stringBuilder.append(this.ltab); 621 | this.stringBuilder.append("FRAME "); 622 | switch(type) { 623 | case -1: 624 | case 0: 625 | this.stringBuilder.append("FULL ["); 626 | this.appendFrameTypes(numLocal, local); 627 | this.stringBuilder.append("] ["); 628 | this.appendFrameTypes(numStack, stack); 629 | this.stringBuilder.append(']'); 630 | break; 631 | case 1: 632 | this.stringBuilder.append("APPEND ["); 633 | this.appendFrameTypes(numLocal, local); 634 | this.stringBuilder.append(']'); 635 | break; 636 | case 2: 637 | this.stringBuilder.append("CHOP ").append(numLocal); 638 | break; 639 | case 3: 640 | this.stringBuilder.append("SAME"); 641 | break; 642 | case 4: 643 | this.stringBuilder.append("SAME1 "); 644 | this.appendFrameTypes(1, stack); 645 | break; 646 | default: 647 | throw new IllegalArgumentException(); 648 | } 649 | 650 | 651 | this.text.add(this.stringBuilder.toString()); 652 | } 653 | 654 | private int rip = 1; 655 | 656 | public void visitInsn(int opcode) { 657 | this.stringBuilder.setLength(0); 658 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]); 659 | this.text.add(this.stringBuilder.toString()); 660 | } 661 | 662 | public void visitIntInsn(int opcode, int operand) { 663 | this.stringBuilder.setLength(0); 664 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' ').append(opcode == 188 ? TYPES[operand] : Integer.toString(operand)); 665 | this.text.add(this.stringBuilder.toString()); 666 | } 667 | 668 | public void visitVarInsn(int opcode, int var) { 669 | this.stringBuilder.setLength(0); 670 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' ').append(var); 671 | this.text.add(this.stringBuilder.toString()); 672 | } 673 | 674 | public void visitTypeInsn(int opcode, String type) { 675 | this.stringBuilder.setLength(0); 676 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' '); 677 | this.appendDescriptor(0, type); 678 | 679 | this.text.add(this.stringBuilder.toString()); 680 | } 681 | 682 | public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { 683 | this.stringBuilder.setLength(0); 684 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' '); 685 | this.appendDescriptor(0, owner); 686 | this.stringBuilder.append('.').append(name).append(" : "); 687 | this.appendDescriptor(1, descriptor); 688 | 689 | this.text.add(this.stringBuilder.toString()); 690 | } 691 | 692 | public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { 693 | this.stringBuilder.setLength(0); 694 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' '); 695 | this.appendDescriptor(0, owner); 696 | this.stringBuilder.append('.').append(name).append(' '); 697 | this.appendDescriptor(3, descriptor); 698 | if (isInterface) { 699 | this.stringBuilder.append(" (itf)"); 700 | } 701 | 702 | 703 | this.text.add(this.stringBuilder.toString()); 704 | } 705 | 706 | public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { 707 | this.stringBuilder.setLength(0); 708 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("INVOKEDYNAMIC").append(' '); 709 | this.stringBuilder.append(name); 710 | this.appendDescriptor(3, descriptor); 711 | this.stringBuilder.append(" ["); 712 | 713 | this.stringBuilder.append(this.tab3); 714 | this.appendHandle(bootstrapMethodHandle); 715 | 716 | this.stringBuilder.append(this.tab3).append("// arguments:"); 717 | if (bootstrapMethodArguments.length == 0) { 718 | this.stringBuilder.append(" none"); 719 | } else { 720 | 721 | Object[] var5 = bootstrapMethodArguments; 722 | int var6 = bootstrapMethodArguments.length; 723 | 724 | for(int var7 = 0; var7 < var6; ++var7) { 725 | Object value = var5[var7]; 726 | this.stringBuilder.append(this.tab3); 727 | if (value instanceof String) { 728 | CustomPrinter.appendString(this.stringBuilder, (String)value); 729 | } else if (value instanceof Type) { 730 | Type type = (Type)value; 731 | if (type.getSort() == 11) { 732 | this.appendDescriptor(3, type.getDescriptor()); 733 | } else { 734 | this.visitType(type); 735 | } 736 | } else if (value instanceof Handle) { 737 | this.appendHandle((Handle)value); 738 | } else { 739 | this.stringBuilder.append(value); 740 | } 741 | 742 | this.stringBuilder.append(", \n"); 743 | } 744 | 745 | this.stringBuilder.setLength(this.stringBuilder.length() - 3); 746 | } 747 | 748 | 749 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("]\n"); 750 | this.text.add(this.stringBuilder.toString()); 751 | } 752 | 753 | public void visitJumpInsn(int opcode, Label label) { 754 | this.stringBuilder.setLength(0); 755 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append(OPCODES[opcode]).append(' '); 756 | this.appendLabel(label); 757 | 758 | this.text.add(this.stringBuilder.toString()); 759 | } 760 | 761 | public void visitLabel(Label label) { 762 | rip++; 763 | this.stringBuilder.setLength(0); 764 | this.stringBuilder.append(this.ltab); 765 | this.appendLabel(label); 766 | 767 | this.text.add(this.stringBuilder.toString()); 768 | } 769 | 770 | public void visitLdcInsn(Object value) { 771 | this.stringBuilder.setLength(0); 772 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("LDC "); 773 | if (value instanceof String) { 774 | CustomPrinter.appendString(this.stringBuilder, (String)value); 775 | } else if (value instanceof Type) { 776 | this.stringBuilder.append(((Type)value).getDescriptor()).append(".class"); 777 | } else { 778 | this.stringBuilder.append(value); 779 | } 780 | 781 | 782 | this.text.add(this.stringBuilder.toString()); 783 | } 784 | 785 | public void visitIincInsn(int var, int increment) { 786 | this.stringBuilder.setLength(0); 787 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("IINC ").append(var).append(' ').append(increment); 788 | this.text.add(this.stringBuilder.toString()); 789 | } 790 | 791 | public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { 792 | this.stringBuilder.setLength(0); 793 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("TABLESWITCH\n"); 794 | 795 | for(int i = 0; i < labels.length; ++i) { 796 | this.stringBuilder.append(this.tab3).append(min + i).append(": "); 797 | this.appendLabel(labels[i]); 798 | 799 | } 800 | 801 | this.stringBuilder.append(this.tab3).append("default: "); 802 | this.appendLabel(dflt); 803 | 804 | this.text.add(this.stringBuilder.toString()); 805 | } 806 | 807 | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 808 | this.stringBuilder.setLength(0); 809 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("LOOKUPSWITCH\n"); 810 | 811 | for(int i = 0; i < labels.length; ++i) { 812 | this.stringBuilder.append(this.tab3).append(keys[i]).append(": "); 813 | this.appendLabel(labels[i]); 814 | 815 | } 816 | 817 | this.stringBuilder.append(this.tab3).append("default: "); 818 | this.appendLabel(dflt); 819 | 820 | this.text.add(this.stringBuilder.toString()); 821 | } 822 | 823 | public void visitMultiANewArrayInsn(String descriptor, int numDimensions) { 824 | this.stringBuilder.setLength(0); 825 | this.stringBuilder.append(this.tab).append(rip++).append(this.tab).append("MULTIANEWARRAY "); 826 | this.appendDescriptor(1, descriptor); 827 | this.stringBuilder.append(' ').append(numDimensions); 828 | this.text.add(this.stringBuilder.toString()); 829 | } 830 | 831 | 832 | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 833 | this.stringBuilder.setLength(0); 834 | this.stringBuilder.append(this.tab2).append("TRYCATCHBLOCK "); 835 | this.appendLabel(start); 836 | this.stringBuilder.append(' '); 837 | this.appendLabel(end); 838 | this.stringBuilder.append(' '); 839 | this.appendLabel(handler); 840 | this.stringBuilder.append(' '); 841 | this.appendDescriptor(0, type); 842 | 843 | this.text.add(this.stringBuilder.toString()); 844 | } 845 | 846 | 847 | public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { 848 | this.stringBuilder.setLength(0); 849 | this.stringBuilder.append(this.tab2).append("LOCALVARIABLE ").append(name).append(' '); 850 | this.appendDescriptor(1, descriptor); 851 | this.stringBuilder.append(' '); 852 | this.appendLabel(start); 853 | this.stringBuilder.append(' '); 854 | this.appendLabel(end); 855 | this.stringBuilder.append(' ').append(index); 856 | if (signature != null) { 857 | this.stringBuilder.append(this.tab2); 858 | this.appendDescriptor(2, signature); 859 | this.stringBuilder.append(this.tab2); 860 | this.appendJavaDeclaration(name, signature); 861 | } 862 | 863 | this.text.add(this.stringBuilder.toString()); 864 | } 865 | 866 | 867 | public void visitLineNumber(int line, Label start) { 868 | this.stringBuilder.setLength(0); 869 | rip++; 870 | this.stringBuilder.append(this.tab2).append("LINENUMBER ").append(line).append(' '); 871 | this.appendLabel(start); 872 | 873 | this.text.add(this.stringBuilder.toString()); 874 | } 875 | 876 | public void visitMaxs(int maxStack, int maxLocals) { 877 | this.stringBuilder.setLength(0); 878 | this.stringBuilder.append(this.tab2).append("MAXSTACK = ").append(maxStack); 879 | this.text.add(this.stringBuilder.toString()); 880 | this.stringBuilder.setLength(0); 881 | this.stringBuilder.append(this.tab2).append("MAXLOCALS = ").append(maxLocals); 882 | this.text.add(this.stringBuilder.toString()); 883 | } 884 | 885 | public void visitMethodEnd() { 886 | } 887 | 888 | 889 | 890 | public void visitAttribute(Attribute attribute) { 891 | this.stringBuilder.setLength(0); 892 | this.stringBuilder.append(this.tab).append("ATTRIBUTE "); 893 | this.appendDescriptor(-1, attribute.type); 894 | if (attribute instanceof TextifierSupport) { 895 | if (this.labelNames == null) { 896 | this.labelNames = new HashMap(); 897 | } 898 | 899 | ((TextifierSupport)attribute).textify(this.stringBuilder, this.labelNames); 900 | } else { 901 | this.stringBuilder.append(" : unknown\n"); 902 | } 903 | 904 | this.text.add(this.stringBuilder.toString()); 905 | } 906 | 907 | private void appendAccess(int accessFlags) { 908 | if ((accessFlags & 1) != 0) { 909 | this.stringBuilder.append("public "); 910 | } 911 | 912 | if ((accessFlags & 2) != 0) { 913 | this.stringBuilder.append("private "); 914 | } 915 | 916 | if ((accessFlags & 4) != 0) { 917 | this.stringBuilder.append("protected "); 918 | } 919 | 920 | if ((accessFlags & 16) != 0) { 921 | this.stringBuilder.append("final "); 922 | } 923 | 924 | if ((accessFlags & 8) != 0) { 925 | this.stringBuilder.append("static "); 926 | } 927 | 928 | if ((accessFlags & 32) != 0) { 929 | this.stringBuilder.append("synchronized "); 930 | } 931 | 932 | if ((accessFlags & 64) != 0) { 933 | this.stringBuilder.append("volatile "); 934 | } 935 | 936 | if ((accessFlags & 128) != 0) { 937 | this.stringBuilder.append("transient "); 938 | } 939 | 940 | if ((accessFlags & 1024) != 0) { 941 | this.stringBuilder.append("abstract "); 942 | } 943 | 944 | if ((accessFlags & 2048) != 0) { 945 | this.stringBuilder.append("strictfp "); 946 | } 947 | 948 | if ((accessFlags & 4096) != 0) { 949 | this.stringBuilder.append("synthetic "); 950 | } 951 | 952 | if ((accessFlags & '耀') != 0) { 953 | this.stringBuilder.append("mandated "); 954 | } 955 | 956 | if ((accessFlags & 16384) != 0) { 957 | this.stringBuilder.append("enum "); 958 | } 959 | 960 | } 961 | 962 | private void appendRawAccess(int accessFlags) { 963 | this.stringBuilder.append("// access flags 0x").append(Integer.toHexString(accessFlags).toUpperCase()); 964 | } 965 | 966 | protected void appendDescriptor(int type, String value) { 967 | if (type != 5 && type != 2 && type != 4) { 968 | this.stringBuilder.append(value); 969 | } else if (value != null) { 970 | this.stringBuilder.append("// signature ").append(value); 971 | } 972 | 973 | } 974 | 975 | private void appendJavaDeclaration(String name, String signature) { 976 | TraceSignatureVisitor traceSignatureVisitor = new TraceSignatureVisitor(this.access); 977 | (new SignatureReader(signature)).accept(traceSignatureVisitor); 978 | this.stringBuilder.append("// declaration: "); 979 | if (traceSignatureVisitor.getReturnType() != null) { 980 | this.stringBuilder.append(traceSignatureVisitor.getReturnType()); 981 | this.stringBuilder.append(' '); 982 | } 983 | 984 | this.stringBuilder.append(name); 985 | this.stringBuilder.append(traceSignatureVisitor.getDeclaration()); 986 | if (traceSignatureVisitor.getExceptions() != null) { 987 | this.stringBuilder.append(" throws ").append(traceSignatureVisitor.getExceptions()); 988 | } 989 | 990 | 991 | } 992 | 993 | protected void appendLabel(Label label) { 994 | if (this.labelNames == null) { 995 | this.labelNames = new HashMap(); 996 | } 997 | 998 | String name = (String)this.labelNames.get(label); 999 | if (name == null) { 1000 | name = "L" + this.labelNames.size(); 1001 | this.labelNames.put(label, name); 1002 | } 1003 | 1004 | this.stringBuilder.append(name); 1005 | } 1006 | 1007 | protected void appendHandle(Handle handle) { 1008 | int tag = handle.getTag(); 1009 | this.stringBuilder.append("// handle kind 0x").append(Integer.toHexString(tag)).append(" : "); 1010 | boolean isMethodHandle = false; 1011 | switch(tag) { 1012 | case 1: 1013 | this.stringBuilder.append("GETFIELD"); 1014 | break; 1015 | case 2: 1016 | this.stringBuilder.append("GETSTATIC"); 1017 | break; 1018 | case 3: 1019 | this.stringBuilder.append("PUTFIELD"); 1020 | break; 1021 | case 4: 1022 | this.stringBuilder.append("PUTSTATIC"); 1023 | break; 1024 | case 5: 1025 | this.stringBuilder.append("INVOKEVIRTUAL"); 1026 | isMethodHandle = true; 1027 | break; 1028 | case 6: 1029 | this.stringBuilder.append("INVOKESTATIC"); 1030 | isMethodHandle = true; 1031 | break; 1032 | case 7: 1033 | this.stringBuilder.append("INVOKESPECIAL"); 1034 | isMethodHandle = true; 1035 | break; 1036 | case 8: 1037 | this.stringBuilder.append("NEWINVOKESPECIAL"); 1038 | isMethodHandle = true; 1039 | break; 1040 | case 9: 1041 | this.stringBuilder.append("INVOKEINTERFACE"); 1042 | isMethodHandle = true; 1043 | break; 1044 | default: 1045 | throw new IllegalArgumentException(); 1046 | } 1047 | 1048 | 1049 | this.stringBuilder.append(this.tab3); 1050 | this.appendDescriptor(0, handle.getOwner()); 1051 | this.stringBuilder.append('.'); 1052 | this.stringBuilder.append(handle.getName()); 1053 | if (!isMethodHandle) { 1054 | this.stringBuilder.append('('); 1055 | } 1056 | 1057 | this.appendDescriptor(9, handle.getDesc()); 1058 | if (!isMethodHandle) { 1059 | this.stringBuilder.append(')'); 1060 | } 1061 | 1062 | if (handle.isInterface()) { 1063 | this.stringBuilder.append(" itf"); 1064 | } 1065 | 1066 | } 1067 | 1068 | private void maybeAppendComma(int numValues) { 1069 | if (numValues > 0) { 1070 | this.stringBuilder.append(", "); 1071 | } 1072 | 1073 | } 1074 | 1075 | private void appendTypeReference(int typeRef) { 1076 | TypeReference typeReference = new TypeReference(typeRef); 1077 | switch(typeReference.getSort()) { 1078 | case 0: 1079 | this.stringBuilder.append("CLASS_TYPE_PARAMETER ").append(typeReference.getTypeParameterIndex()); 1080 | break; 1081 | case 1: 1082 | this.stringBuilder.append("METHOD_TYPE_PARAMETER ").append(typeReference.getTypeParameterIndex()); 1083 | break; 1084 | case 2: 1085 | case 3: 1086 | case 4: 1087 | case 5: 1088 | case 6: 1089 | case 7: 1090 | case 8: 1091 | case 9: 1092 | case 10: 1093 | case 11: 1094 | case 12: 1095 | case 13: 1096 | case 14: 1097 | case 15: 1098 | case 24: 1099 | case 25: 1100 | case 26: 1101 | case 27: 1102 | case 28: 1103 | case 29: 1104 | case 30: 1105 | case 31: 1106 | case 32: 1107 | case 33: 1108 | case 34: 1109 | case 35: 1110 | case 36: 1111 | case 37: 1112 | case 38: 1113 | case 39: 1114 | case 40: 1115 | case 41: 1116 | case 42: 1117 | case 43: 1118 | case 44: 1119 | case 45: 1120 | case 46: 1121 | case 47: 1122 | case 48: 1123 | case 49: 1124 | case 50: 1125 | case 51: 1126 | case 52: 1127 | case 53: 1128 | case 54: 1129 | case 55: 1130 | case 56: 1131 | case 57: 1132 | case 58: 1133 | case 59: 1134 | case 60: 1135 | case 61: 1136 | case 62: 1137 | case 63: 1138 | default: 1139 | throw new IllegalArgumentException(); 1140 | case 16: 1141 | this.stringBuilder.append("CLASS_EXTENDS ").append(typeReference.getSuperTypeIndex()); 1142 | break; 1143 | case 17: 1144 | this.stringBuilder.append("CLASS_TYPE_PARAMETER_BOUND ").append(typeReference.getTypeParameterIndex()).append(", ").append(typeReference.getTypeParameterBoundIndex()); 1145 | break; 1146 | case 18: 1147 | this.stringBuilder.append("METHOD_TYPE_PARAMETER_BOUND ").append(typeReference.getTypeParameterIndex()).append(", ").append(typeReference.getTypeParameterBoundIndex()); 1148 | break; 1149 | case 19: 1150 | this.stringBuilder.append("FIELD"); 1151 | break; 1152 | case 20: 1153 | this.stringBuilder.append("METHOD_RETURN"); 1154 | break; 1155 | case 21: 1156 | this.stringBuilder.append("METHOD_RECEIVER"); 1157 | break; 1158 | case 22: 1159 | this.stringBuilder.append("METHOD_FORMAL_PARAMETER ").append(typeReference.getFormalParameterIndex()); 1160 | break; 1161 | case 23: 1162 | this.stringBuilder.append("THROWS ").append(typeReference.getExceptionIndex()); 1163 | break; 1164 | case 64: 1165 | this.stringBuilder.append("LOCAL_VARIABLE"); 1166 | break; 1167 | case 65: 1168 | this.stringBuilder.append("RESOURCE_VARIABLE"); 1169 | break; 1170 | case 66: 1171 | this.stringBuilder.append("EXCEPTION_PARAMETER ").append(typeReference.getTryCatchBlockIndex()); 1172 | break; 1173 | case 67: 1174 | this.stringBuilder.append("INSTANCEOF"); 1175 | break; 1176 | case 68: 1177 | this.stringBuilder.append("NEW"); 1178 | break; 1179 | case 69: 1180 | this.stringBuilder.append("CONSTRUCTOR_REFERENCE"); 1181 | break; 1182 | case 70: 1183 | this.stringBuilder.append("METHOD_REFERENCE"); 1184 | break; 1185 | case 71: 1186 | this.stringBuilder.append("CAST ").append(typeReference.getTypeArgumentIndex()); 1187 | break; 1188 | case 72: 1189 | this.stringBuilder.append("CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT ").append(typeReference.getTypeArgumentIndex()); 1190 | break; 1191 | case 73: 1192 | this.stringBuilder.append("METHOD_INVOCATION_TYPE_ARGUMENT ").append(typeReference.getTypeArgumentIndex()); 1193 | break; 1194 | case 74: 1195 | this.stringBuilder.append("CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT ").append(typeReference.getTypeArgumentIndex()); 1196 | break; 1197 | case 75: 1198 | this.stringBuilder.append("METHOD_REFERENCE_TYPE_ARGUMENT ").append(typeReference.getTypeArgumentIndex()); 1199 | } 1200 | 1201 | } 1202 | 1203 | private void appendFrameTypes(int numTypes, Object[] frameTypes) { 1204 | for(int i = 0; i < numTypes; ++i) { 1205 | if (i > 0) { 1206 | this.stringBuilder.append(' '); 1207 | } 1208 | 1209 | if (frameTypes[i] instanceof String) { 1210 | String descriptor = (String)frameTypes[i]; 1211 | if (descriptor.charAt(0) == '[') { 1212 | this.appendDescriptor(1, descriptor); 1213 | } else { 1214 | this.appendDescriptor(0, descriptor); 1215 | } 1216 | } else if (frameTypes[i] instanceof Integer) { 1217 | this.stringBuilder.append((String)FRAME_TYPES.get((Integer)frameTypes[i])); 1218 | } else { 1219 | this.appendLabel((Label)frameTypes[i]); 1220 | } 1221 | } 1222 | 1223 | } 1224 | 1225 | private CustomTextifier addNewTextifier(String endText) { 1226 | CustomTextifier textifier = this.createTextifier(); 1227 | this.text.add(textifier.text); 1228 | if (endText != null) { 1229 | this.text.add(endText); 1230 | } 1231 | 1232 | return textifier; 1233 | } 1234 | 1235 | protected CustomTextifier createTextifier() { 1236 | return new CustomTextifier(this.api); 1237 | } 1238 | } 1239 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/utils/PostPreState.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils; 2 | 3 | public enum PostPreState { 4 | 5 | PRE, 6 | POST; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/dragoncommissions/mixbukkit/utils/io/BukkitErrorOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils.io; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.ChatColor; 5 | 6 | import java.io.*; 7 | import java.util.Scanner; 8 | 9 | public class BukkitErrorOutputStream extends OutputStream { 10 | 11 | 12 | public BukkitErrorOutputStream() { 13 | 14 | } 15 | 16 | @Override 17 | public void write(int b) throws IOException { 18 | if (b == '\n') { 19 | System.err.print("\u001B[0m"); 20 | } 21 | System.err.write(b); 22 | if (b == '\n') { 23 | System.err.print("\u001B[31m"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Agent-Class: com.dragoncommissions.mixbukkit.agent.AgentMain 3 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | debug: false -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: MixBukkit 2 | version: 0.1 3 | main: com.dragoncommissions.mixbukkit.MixBukkit --------------------------------------------------------------------------------