├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ └── java │ └── cc │ └── nuym │ └── jnic │ ├── utils │ ├── Platform.java │ ├── LabelPool.java │ ├── InsnBuilder.java │ ├── CatchesBlock.java │ ├── EncodeUtils.java │ ├── InterfaceStaticClassProvider.java │ ├── CryptoUtils.java │ ├── TamperUtils.java │ ├── BootstrapMethodsPool.java │ ├── MethodContext.java │ ├── IOUtils.java │ ├── HexUtil.java │ ├── Zipper.java │ ├── Base64Util.java │ ├── FileUtils.java │ ├── ConsoleColors.java │ ├── ClassMethodFilter.java │ ├── ASMUtils.java │ ├── StringUtils.java │ └── DecryptorClass.java │ ├── special │ ├── SpecialMethodProcessor.java │ ├── ClInitSpecialMethodProcessor.java │ └── DefaultSpecialMethodProcessor.java │ ├── instructions │ ├── InstructionTypeHandler.java │ ├── LineNumberHandler.java │ ├── IincHandler.java │ ├── InstructionHandlerContainer.java │ ├── IntHandler.java │ ├── LabelHandler.java │ ├── VarHandler.java │ ├── TypeHandler.java │ ├── JumpHandler.java │ ├── TableSwitchHandler.java │ ├── LookupSwitchHandler.java │ ├── FieldHandler.java │ ├── LdcHandler.java │ ├── FrameHandler.java │ ├── MultiANewArrayHandler.java │ ├── InsnHandler.java │ ├── GenericInstructionHandler.java │ └── MethodHandler.java │ ├── cache │ ├── NodeCache.java │ ├── FieldNodeCache.java │ ├── MethodNodeCache.java │ ├── CachedFieldInfo.java │ ├── CachedMethodInfo.java │ ├── ClassNodeCache.java │ └── CachedClassInfo.java │ ├── xml │ ├── Match.java │ └── Config.java │ ├── asm │ ├── SafeClassWriter.java │ └── ClassMetadataReader.java │ ├── NativeSignature.java │ ├── helpers │ └── ProcessHelper.java │ ├── env │ └── SetupManager.java │ └── Main.java ├── settings.gradle ├── annotation ├── build.gradle └── src │ └── main │ └── java │ └── cc │ └── nuym │ └── jnic │ └── annotations │ ├── NotNative.java │ ├── Heavy.java │ └── Native.java ├── loader ├── build.gradle └── src │ └── main │ └── java │ └── JNICLoader.java ├── README-CN.md ├── README.md ├── .github └── workflows │ └── gradle-publish.yml ├── gradlew.bat ├── .gitignore └── gradlew /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuym/j2c/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/Platform.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | public enum Platform 4 | { 5 | HOTSPOT, 6 | STD_JAVA; 7 | } 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | */ 4 | 5 | rootProject.name = 'Jnic' 6 | include 'loader' 7 | include 'annotation' 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/special/SpecialMethodProcessor.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.special; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | 5 | public interface SpecialMethodProcessor 6 | { 7 | String preprocess(final MethodContext p0); 8 | 9 | void postprocess(final MethodContext p0); 10 | } 11 | -------------------------------------------------------------------------------- /annotation/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'cc.nuym.jnic' 6 | version '3.6.1' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' 14 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /loader/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'cc.nuym.jnic' 6 | version '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' 14 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/InstructionTypeHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | 6 | public interface InstructionTypeHandler { 7 | void accept(MethodContext context, T node); 8 | 9 | String insnToString(MethodContext context, T node); 10 | 11 | int getNewStackPointer(T node, int currentStackPointer); 12 | } 13 | -------------------------------------------------------------------------------- /annotation/src/main/java/cc/nuym/jnic/annotations/NotNative.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation that allows to ignore method from native obfuscation 10 | */ 11 | @Retention(RetentionPolicy.CLASS) 12 | @Target({ElementType.METHOD}) 13 | public @interface NotNative { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/LabelPool.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.utils; 3 | 4 | import org.objectweb.asm.Label; 5 | 6 | import java.util.WeakHashMap; 7 | 8 | public class LabelPool { 9 | private final WeakHashMap labels = new WeakHashMap<>(); 10 | private long currentIndex = 0L; 11 | 12 | public String getName(Label label) { 13 | return "L" + this.labels.computeIfAbsent(label, addedLabel -> ++this.currentIndex); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /annotation/src/main/java/cc/nuym/jnic/annotations/Heavy.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation that allows to include method or class to native obfuscation 10 | */ 11 | @Retention(RetentionPolicy.CLASS) 12 | @Target({ElementType.METHOD, ElementType.TYPE}) 13 | public @interface Heavy { 14 | } 15 | -------------------------------------------------------------------------------- /annotation/src/main/java/cc/nuym/jnic/annotations/Native.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation that allows to include method or class to native obfuscation 10 | */ 11 | @Retention(RetentionPolicy.CLASS) 12 | @Target({ElementType.METHOD, ElementType.TYPE}) 13 | public @interface Native { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/LineNumberHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import org.objectweb.asm.tree.LineNumberNode; 5 | 6 | public class LineNumberHandler implements InstructionTypeHandler 7 | { 8 | @Override 9 | public void accept(final MethodContext context, final LineNumberNode node) { 10 | context.line = node.line; 11 | } 12 | 13 | @Override 14 | public String insnToString(final MethodContext context, final LineNumberNode node) { 15 | return String.format("Line %d", node.line); 16 | } 17 | 18 | @Override 19 | public int getNewStackPointer(final LineNumberNode node, final int currentStackPointer) { 20 | return currentStackPointer; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/IincHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import org.objectweb.asm.tree.IincInsnNode; 5 | 6 | public class IincHandler extends GenericInstructionHandler 7 | { 8 | @Override 9 | protected void process(final MethodContext context, final IincInsnNode node) { 10 | this.props.put("incr", String.valueOf(node.incr)); 11 | this.props.put("var", String.valueOf(node.var)); 12 | } 13 | 14 | @Override 15 | public String insnToString(final MethodContext context, final IincInsnNode node) { 16 | return String.format("IINC %d %d", node.var, node.incr); 17 | } 18 | 19 | @Override 20 | public int getNewStackPointer(final IincInsnNode node, final int currentStackPointer) { 21 | return currentStackPointer; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/NodeCache.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class NodeCache 7 | { 8 | private final Map cache; 9 | 10 | public NodeCache() { 11 | this.cache = new HashMap<>(); 12 | } 13 | 14 | public void getPointer(final T key) { 15 | this.getId(key); 16 | } 17 | 18 | public int getId(final T key) { 19 | if (!this.cache.containsKey(key)) { 20 | this.cache.put(key, this.cache.size()); 21 | } 22 | return this.cache.get(key); 23 | } 24 | 25 | public int size() { 26 | return this.cache.size(); 27 | } 28 | 29 | public boolean isEmpty() { 30 | return this.cache.isEmpty(); 31 | } 32 | 33 | public Map getCache() { 34 | return this.cache; 35 | } 36 | 37 | public void clear() { 38 | this.cache.clear(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/xml/Match.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.xml; 2 | 3 | 4 | import org.simpleframework.xml.Attribute; 5 | 6 | public class Match { 7 | 8 | @Attribute(name = "className", required=false) 9 | private String className; 10 | 11 | @Attribute(name = "methodName",required=false) 12 | private String methodName; 13 | 14 | @Attribute(name = "methodDesc",required=false) 15 | private String methodDesc; 16 | 17 | public String getClassName() { 18 | return className; 19 | } 20 | 21 | public void setClassName(String className) { 22 | this.className = className; 23 | } 24 | 25 | public String getMethodName() { 26 | return methodName; 27 | } 28 | 29 | public void setMethodName(String methodName) { 30 | this.methodName = methodName; 31 | } 32 | 33 | public String getMethodDesc() { 34 | return methodDesc; 35 | } 36 | 37 | public void setMethodDesc(String methodDesc) { 38 | this.methodDesc = methodDesc; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/InstructionHandlerContainer.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import org.objectweb.asm.tree.AbstractInsnNode; 5 | 6 | public class InstructionHandlerContainer { 7 | 8 | private final InstructionTypeHandler handler; 9 | private final Class clazz; 10 | 11 | public InstructionHandlerContainer(InstructionTypeHandler handler, Class clazz) { 12 | this.handler = handler; 13 | this.clazz = clazz; 14 | } 15 | 16 | public void accept(MethodContext context, AbstractInsnNode node) { 17 | handler.accept(context, clazz.cast(node)); 18 | } 19 | 20 | public String insnToString(MethodContext context, AbstractInsnNode node) { 21 | return handler.insnToString(context, clazz.cast(node)); 22 | } 23 | 24 | public int getNewStackPointer(AbstractInsnNode node, int stackPointer) { 25 | return handler.getNewStackPointer(clazz.cast(node), stackPointer); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/InsnBuilder.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.objectweb.asm.tree.AbstractInsnNode; 4 | import org.objectweb.asm.tree.InsnList; 5 | 6 | import java.util.Arrays; 7 | 8 | public class InsnBuilder { 9 | 10 | private final InsnList insnList; 11 | 12 | private InsnBuilder() { 13 | this.insnList = new InsnList(); 14 | } 15 | 16 | private InsnBuilder(InsnList insnList) { 17 | this.insnList = insnList; 18 | } 19 | 20 | public InsnBuilder insn(AbstractInsnNode... insnNodes) { 21 | Arrays.stream(insnNodes).forEach(this.insnList::add); 22 | return this; 23 | } 24 | 25 | public InsnBuilder insnList(InsnList... insnLists) { 26 | Arrays.stream(insnLists).forEach(this.insnList::add); 27 | return this; 28 | } 29 | 30 | public InsnList getInsnList() { 31 | return insnList; 32 | } 33 | 34 | public static InsnBuilder create(InsnList insnList) { 35 | return new InsnBuilder(insnList); 36 | } 37 | 38 | public static InsnBuilder createEmpty() { 39 | return new InsnBuilder(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # nuym's jnic 2 | 3 | > Will be open-sourced eventually 4 | 5 | Open source and performance close to JNIC (excluding Zig performance). 6 | 7 | | Obfuscator | Test#1 | Test#2 | Performance | Size | 8 | |--------------------------------------------------------------------|---------|----------|-------------|-------| 9 | | [None](https://www.java.com/) | PPPPPPP | PPPPPPPP | 27ms | 29KB | 10 | | [JNIC-3.5.1](https://jnic.dev/) | PPPPPPP | FPPPEPPP | 481ms | 89KB | 11 | | [nuym's JNIC](https://github.com/nuym/j2c) | PPPPPPP | FPPPEPPP | 653ms(because of Zig 11.0) | 92KB | 12 | 13 | # DMCA 14 | This project only merely uses the package name `dev.jnic` which should not inherently violate any copyright. If you have any questions regarding copyright issues, please email `1006800345@qq.com`. 15 | 16 | # License 17 | This project is licensed under the [GNU General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/). 18 | 19 | # Special Thanks 20 | - [native-obfuscator](https://github.com/radioegor146/native-obfuscator) 21 | - [JNIC](jnic.dev) 22 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/IntHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.IntInsnNode; 7 | 8 | public class IntHandler extends GenericInstructionHandler { 9 | 10 | @Override 11 | protected void process(MethodContext context, IntInsnNode node) { 12 | props.put("operand", String.valueOf(node.operand)); 13 | if (node.getOpcode() == Opcodes.NEWARRAY) { 14 | instructionName += "_" + node.operand; 15 | } 16 | } 17 | 18 | @Override 19 | public String insnToString(MethodContext context, IntInsnNode node) { 20 | return String.format("%s %d", Util.getOpcodeString(node.getOpcode()), node.operand); 21 | } 22 | 23 | @Override 24 | public int getNewStackPointer(IntInsnNode node, int currentStackPointer) { 25 | switch (node.getOpcode()) { 26 | case Opcodes.BIPUSH: 27 | case Opcodes.SIPUSH: 28 | return currentStackPointer + 1; 29 | case Opcodes.NEWARRAY: 30 | return currentStackPointer; 31 | } 32 | throw new RuntimeException(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/xml/Config.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.xml; 2 | 3 | import org.simpleframework.xml.Element; 4 | import org.simpleframework.xml.ElementList; 5 | import org.simpleframework.xml.Root; 6 | 7 | import java.util.List; 8 | 9 | @Root(name = "jnic") 10 | public class Config { 11 | 12 | @ElementList(name = "targets") 13 | private List targets; 14 | 15 | 16 | public List getPrintmsg() { 17 | return printmsg; 18 | } 19 | 20 | @ElementList(name = "printmsg",required = false) 21 | private List printmsg; 22 | 23 | @ElementList(name = "include", type = Match.class, required = false) 24 | private List includes; 25 | 26 | @ElementList(name = "exclude", type = Match.class, required = false) 27 | private List excludes; 28 | 29 | 30 | 31 | public List getTargets() { 32 | return targets; 33 | } 34 | 35 | public void setTargets(List targets) { 36 | this.targets = targets; 37 | } 38 | 39 | public List getIncludes() { 40 | return includes; 41 | } 42 | 43 | public void setIncludes(List includes) { 44 | this.includes = includes; 45 | } 46 | 47 | public List getExcludes() { 48 | return excludes; 49 | } 50 | 51 | public void setExcludes(List excludes) { 52 | this.excludes = excludes; 53 | } 54 | 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/asm/SafeClassWriter.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.asm; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassWriter; 5 | 6 | import java.util.ArrayList; 7 | 8 | public class SafeClassWriter extends ClassWriter 9 | { 10 | private final ClassMetadataReader classMetadataReader; 11 | 12 | public SafeClassWriter(final ClassMetadataReader classMetadataReader, final int flags) { 13 | super(flags); 14 | this.classMetadataReader = classMetadataReader; 15 | } 16 | 17 | public SafeClassWriter(final ClassReader classReader, final ClassMetadataReader classMetadataReader, final int flags) { 18 | super(classReader, flags); 19 | this.classMetadataReader = classMetadataReader; 20 | } 21 | 22 | @Override 23 | protected String getCommonSuperClass(final String type1, final String type2) { 24 | ArrayList superClasses1; 25 | ArrayList superClasses2; 26 | int size; 27 | int i; 28 | for (superClasses1 = this.classMetadataReader.getSuperClasses(type1), superClasses2 = this.classMetadataReader.getSuperClasses(type2), size = Math.min(superClasses1.size(), superClasses2.size()), i = 0; i < size && superClasses1.get(i).equals(superClasses2.get(i)); ++i) {} 29 | if (i == 0) { 30 | return "java/lang/Object"; 31 | } 32 | return superClasses1.get(i - 1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/LabelHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import org.objectweb.asm.tree.LabelNode; 5 | 6 | public class LabelHandler extends GenericInstructionHandler { 7 | 8 | @Override 9 | public void accept(MethodContext context, LabelNode node) { 10 | context.method.tryCatchBlocks.stream().filter(x -> x.start.equals(node)) 11 | .forEachOrdered(context.tryCatches::add); 12 | context.method.tryCatchBlocks.stream().filter(x -> x.end.equals(node)) 13 | .forEachOrdered(context.tryCatches::remove); 14 | try { 15 | super.accept(context, node); 16 | } catch (UnsupportedOperationException ex) { 17 | // ignored 18 | } 19 | 20 | context.output.append(String.format("%s:;\n", context.getLabelPool().getName(node.getLabel()))); 21 | } 22 | 23 | @Override 24 | public String insnToString(MethodContext context, LabelNode node) { 25 | return String.format("LABEL %s", context.getLabelPool().getName(node.getLabel())); 26 | } 27 | 28 | @Override 29 | public int getNewStackPointer(LabelNode node, int currentStackPointer) { 30 | return currentStackPointer; 31 | } 32 | 33 | @Override 34 | protected void process(MethodContext context, LabelNode node) { 35 | throw new UnsupportedOperationException("break at super.process()"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/VarHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.VarInsnNode; 7 | 8 | public class VarHandler extends GenericInstructionHandler { 9 | 10 | @Override 11 | protected void process(MethodContext context, VarInsnNode node) { 12 | props.put("var", String.valueOf(node.var)); 13 | } 14 | 15 | @Override 16 | public String insnToString(MethodContext context, VarInsnNode node) { 17 | return String.format("%s %d", Util.getOpcodeString(node.getOpcode()), node.var); 18 | } 19 | 20 | @Override 21 | public int getNewStackPointer(VarInsnNode node, int currentStackPointer) { 22 | switch (node.getOpcode()) { 23 | case Opcodes.ILOAD: 24 | case Opcodes.FLOAD: 25 | case Opcodes.ALOAD: 26 | return currentStackPointer + 1; 27 | case Opcodes.LLOAD: 28 | case Opcodes.DLOAD: 29 | return currentStackPointer + 2; 30 | case Opcodes.ISTORE: 31 | case Opcodes.FSTORE: 32 | case Opcodes.ASTORE: 33 | return currentStackPointer - 1; 34 | case Opcodes.LSTORE: 35 | case Opcodes.DSTORE: 36 | return currentStackPointer - 2; 37 | } 38 | throw new RuntimeException(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuym's jnic 2 | > New version in development 3 | 4 | Open source and performance close to JNIC (excluding Zig performance). 5 | 6 | | Obfuscator | Test#1 | Test#2 | Performance | Size | 7 | |--------------------------------------------|---------|----------|----------------------------------------|-------| 8 | | [None](https://www.java.com/) | PPPPPPP | PPPPPPPP | 27ms | 29KB | 9 | | [JNIC-3.5.1](https://jnic.dev/) | PPPPPPP | FPPPEPPP | 481ms | 89KB | 10 | | [nuym's JNIC](https://github.com/nuym/j2c) | PPPPPPP | FPPPEPPP | 653ms (bottleneck caused by Zig 11.0) | 92KB | 11 | 12 | # License 13 | This project is licensed under the [GNU General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/). 14 | 15 | # DMCA 16 | If you believe your code was added unwillingly added into this project, please contact `1006800345@qq.com` to resolve that issue. 17 | 18 | # Special Thanks 19 | - [native-obfuscator](https://github.com/radioegor146/native-obfuscator) 20 | - [JNIC](https://jnic.dev/) 21 | - [JavaObfuscatorTest](https://github.com/huzpsb/JavaObfuscatorTest) 22 | - [Open-Myj2c](https://github.com/MyJ2c/Open-MyJ2c) 23 | - [Bozar](https://github.com/vimasig/Bozar) 24 | 25 | Special thanks to these individuals for their contribution(s): 26 | - [ImFl0wow](https://github.com/ImFl0wow) 27 | - [huzpsb](https://github.com/huzpsb) 28 | - [ts](https://github.com/uniformization) 29 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/FieldNodeCache.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class FieldNodeCache 7 | { 8 | private final String pointerPattern; 9 | private final Map cache; 10 | private final ClassNodeCache classNodeCache; 11 | 12 | public FieldNodeCache(final String pointerPattern, final ClassNodeCache classNodeCache) { 13 | this.pointerPattern = pointerPattern; 14 | this.classNodeCache = classNodeCache; 15 | this.cache = new HashMap<>(); 16 | } 17 | 18 | public String getPointer(final CachedFieldInfo fieldInfo) { 19 | return String.format(this.pointerPattern, this.getId(fieldInfo)); 20 | } 21 | 22 | public int getId(final CachedFieldInfo fieldInfo) { 23 | if (!this.cache.containsKey(fieldInfo)) { 24 | final CachedClassInfo classInfo = this.classNodeCache.getClass(fieldInfo.getClazz()); 25 | classInfo.addCachedField(fieldInfo); 26 | this.cache.put(fieldInfo, this.cache.size()); 27 | } 28 | return this.cache.get(fieldInfo); 29 | } 30 | 31 | public int size() { 32 | return this.cache.size(); 33 | } 34 | 35 | public boolean isEmpty() { 36 | return this.cache.isEmpty(); 37 | } 38 | 39 | public Map getCache() { 40 | return this.cache; 41 | } 42 | 43 | public void clear() { 44 | this.cache.clear(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/MethodNodeCache.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MethodNodeCache 7 | { 8 | private final String pointerPattern; 9 | private final Map cache; 10 | private final ClassNodeCache classNodeCache; 11 | 12 | public MethodNodeCache(final String pointerPattern, final ClassNodeCache classNodeCache) { 13 | this.pointerPattern = pointerPattern; 14 | this.classNodeCache = classNodeCache; 15 | this.cache = new HashMap<>(); 16 | } 17 | 18 | public String getPointer(final CachedMethodInfo methodInfo) { 19 | return String.format(this.pointerPattern, this.getId(methodInfo)); 20 | } 21 | 22 | public int getId(final CachedMethodInfo methodInfo) { 23 | if (!this.cache.containsKey(methodInfo)) { 24 | final CachedClassInfo classInfo = this.classNodeCache.getClass(methodInfo.getClazz()); 25 | classInfo.addCachedMethod(methodInfo); 26 | this.cache.put(methodInfo, this.cache.size()); 27 | } 28 | return this.cache.get(methodInfo); 29 | } 30 | 31 | public int size() { 32 | return this.cache.size(); 33 | } 34 | 35 | public boolean isEmpty() { 36 | return this.cache.isEmpty(); 37 | } 38 | 39 | public Map getCache() { 40 | return this.cache; 41 | } 42 | 43 | public void clear() { 44 | this.cache.clear(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/gradle-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created 6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle 7 | 8 | name: Gradle Package 9 | 10 | on: 11 | release: 12 | types: [created] 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Set up JDK 11 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '11' 28 | distribution: 'temurin' 29 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 30 | settings-path: ${{ github.workspace }} # location for the settings.xml file 31 | 32 | - name: Build with Gradle 33 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 34 | with: 35 | arguments: build 36 | 37 | # The USERNAME and TOKEN need to correspond to the credentials environment variables used in 38 | # the publishing section of your build.gradle 39 | - name: Publish to GitHub Packages 40 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 41 | with: 42 | arguments: publish 43 | env: 44 | USERNAME: ${{ github.actor }} 45 | TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/TypeHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.MethodProcessor; 5 | import cc.nuym.jnic.utils.Util; 6 | import org.objectweb.asm.Opcodes; 7 | import org.objectweb.asm.tree.TypeInsnNode; 8 | 9 | public class TypeHandler extends GenericInstructionHandler 10 | { 11 | @Override 12 | protected void process(final MethodContext context, final TypeInsnNode node) { 13 | this.props.put("desc", node.desc); 14 | this.props.put("class_ptr", "c_" + context.getCachedClasses().getId(node.desc) + "_"); 15 | final String instructionName = MethodProcessor.INSTRUCTIONS.getOrDefault(node.getOpcode(), "NOTFOUND"); 16 | if ("CHECKCAST".equals(instructionName)) { 17 | this.props.put("exception_ptr", "c_" + context.getCachedClasses().getId("java/lang/ClassCastException") + "_"); 18 | } 19 | this.props.put("desc_ptr", node.desc); 20 | } 21 | 22 | @Override 23 | public String insnToString(final MethodContext context, final TypeInsnNode node) { 24 | return String.format("%s %s", Util.getOpcodeString(node.getOpcode()), node.desc); 25 | } 26 | 27 | @Override 28 | public int getNewStackPointer(final TypeInsnNode node, final int currentStackPointer) { 29 | switch (node.getOpcode()) { 30 | case Opcodes.ANEWARRAY: 31 | case Opcodes.CHECKCAST: 32 | case Opcodes.INSTANCEOF: 33 | return currentStackPointer; 34 | case Opcodes.NEW: 35 | return currentStackPointer + 1; 36 | } 37 | throw new RuntimeException(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/JumpHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.JumpInsnNode; 7 | 8 | public class JumpHandler extends GenericInstructionHandler { 9 | 10 | @Override 11 | protected void process(MethodContext context, JumpInsnNode node) { 12 | props.put("label", String.valueOf(context.getLabelPool().getName(node.label.getLabel()))); 13 | } 14 | 15 | @Override 16 | public String insnToString(MethodContext methodContext, JumpInsnNode node) { 17 | return String.format("%s %s", Util.getOpcodeString(node.getOpcode()), methodContext.getLabelPool().getName(node.label.getLabel())); 18 | } 19 | 20 | @Override 21 | public int getNewStackPointer(JumpInsnNode node, int currentStackPointer) { 22 | switch (node.getOpcode()) { 23 | case Opcodes.IFEQ: 24 | case Opcodes.IFNE: 25 | case Opcodes.IFLT: 26 | case Opcodes.IFGE: 27 | case Opcodes.IFGT: 28 | case Opcodes.IFLE: 29 | case Opcodes.IFNULL: 30 | case Opcodes.IFNONNULL: 31 | return currentStackPointer - 1; 32 | case Opcodes.IF_ICMPEQ: 33 | case Opcodes.IF_ICMPNE: 34 | case Opcodes.IF_ICMPLT: 35 | case Opcodes.IF_ICMPGE: 36 | case Opcodes.IF_ICMPGT: 37 | case Opcodes.IF_ICMPLE: 38 | case Opcodes.IF_ACMPEQ: 39 | case Opcodes.IF_ACMPNE: 40 | return currentStackPointer - 2; 41 | case Opcodes.GOTO: 42 | return currentStackPointer; 43 | } 44 | throw new RuntimeException(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/CachedFieldInfo.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.Objects; 4 | 5 | public class CachedFieldInfo 6 | { 7 | private final String clazz; 8 | private final String name; 9 | private final String desc; 10 | private final boolean isStatic; 11 | private int id; 12 | 13 | public CachedFieldInfo(final String clazz, final String name, final String desc, final boolean isStatic) { 14 | this.clazz = clazz; 15 | this.name = name; 16 | this.desc = desc; 17 | this.isStatic = isStatic; 18 | this.id = -1; 19 | } 20 | 21 | @Override 22 | public boolean equals(final Object o) { 23 | if (this == o) { 24 | return true; 25 | } 26 | if (o == null || this.getClass() != o.getClass()) { 27 | return false; 28 | } 29 | final CachedFieldInfo that = (CachedFieldInfo)o; 30 | return this.isStatic == that.isStatic && this.clazz.equals(that.clazz) && this.name.equals(that.name) && this.desc.equals(that.desc); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return Objects.hash(this.clazz, this.name, this.desc, this.isStatic); 36 | } 37 | 38 | public String getClazz() { 39 | return this.clazz; 40 | } 41 | 42 | public boolean isStatic() { 43 | return this.isStatic; 44 | } 45 | 46 | public String getName() { 47 | return this.name; 48 | } 49 | 50 | public String getDesc() { 51 | return this.desc; 52 | } 53 | 54 | public int getId() { 55 | return this.id; 56 | } 57 | 58 | public void setId(final int id) { 59 | this.id = id; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "CachedFieldInfo{clazz='" + this.clazz + '\'' + ", name='" + this.name + '\'' + ", desc='" + this.desc + '\'' + ", isStatic=" + this.isStatic + '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/CachedMethodInfo.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.Objects; 4 | 5 | public class CachedMethodInfo 6 | { 7 | private final String clazz; 8 | private final String name; 9 | private final String desc; 10 | private final boolean isStatic; 11 | private int id; 12 | 13 | public CachedMethodInfo(final String clazz, final String name, final String desc, final boolean isStatic) { 14 | this.clazz = clazz; 15 | this.name = name; 16 | this.desc = desc; 17 | this.isStatic = isStatic; 18 | this.id = -1; 19 | } 20 | 21 | @Override 22 | public boolean equals(final Object o) { 23 | if (this == o) { 24 | return true; 25 | } 26 | if (o == null || this.getClass() != o.getClass()) { 27 | return false; 28 | } 29 | final CachedMethodInfo that = (CachedMethodInfo)o; 30 | return this.isStatic == that.isStatic && this.clazz.equals(that.clazz) && this.name.equals(that.name) && this.desc.equals(that.desc); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return Objects.hash(this.clazz, this.name, this.desc, this.isStatic); 36 | } 37 | 38 | public String getClazz() { 39 | return this.clazz; 40 | } 41 | 42 | public String getName() { 43 | return this.name; 44 | } 45 | 46 | public String getDesc() { 47 | return this.desc; 48 | } 49 | 50 | public boolean isStatic() { 51 | return this.isStatic; 52 | } 53 | 54 | public int getId() { 55 | return this.id; 56 | } 57 | 58 | public void setId(final int id) { 59 | this.id = id; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "CachedMethodInfo{clazz='" + this.clazz + '\'' + ", name='" + this.name + '\'' + ", desc='" + this.desc + '\'' + ", isStatic=" + this.isStatic + '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/TableSwitchHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.tree.TableSwitchInsnNode; 7 | 8 | public class TableSwitchHandler extends GenericInstructionHandler 9 | { 10 | @Override 11 | protected void process(final MethodContext context, final TableSwitchInsnNode node) { 12 | final StringBuilder output = context.output; 13 | output.append(getStart(context)).append("\n "); 14 | for (int i = 0; i < node.labels.size(); ++i) { 15 | output.append(String.format(" %s\n ", getPart(context, node.min + i, node.labels.get(i).getLabel()))); 16 | } 17 | output.append(String.format(" %s\n ", getDefault(context, node.dflt.getLabel()))); 18 | this.instructionName = "TABLESWITCH_END"; 19 | } 20 | 21 | private static String getStart(final MethodContext context) { 22 | return context.getSnippets().getSnippet("TABLESWITCH_START", Util.createMap("stackindexm1", String.valueOf(context.stackPointer - 1))); 23 | } 24 | 25 | private static String getPart(final MethodContext context, final int index, final Label label) { 26 | return context.getSnippets().getSnippet("TABLESWITCH_PART", Util.createMap("index", index, "label", context.getLabelPool().getName(label))); 27 | } 28 | 29 | private static String getDefault(final MethodContext context, final Label label) { 30 | return context.getSnippets().getSnippet("TABLESWITCH_DEFAULT", Util.createMap("label", context.getLabelPool().getName(label))); 31 | } 32 | 33 | @Override 34 | public String insnToString(final MethodContext context, final TableSwitchInsnNode node) { 35 | return "TABLESWITCH"; 36 | } 37 | 38 | @Override 39 | public int getNewStackPointer(final TableSwitchInsnNode node, final int currentStackPointer) { 40 | return currentStackPointer - 1; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/LookupSwitchHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.tree.LookupSwitchInsnNode; 7 | 8 | public class LookupSwitchHandler extends GenericInstructionHandler 9 | { 10 | @Override 11 | protected void process(final MethodContext context, final LookupSwitchInsnNode node) { 12 | final StringBuilder output = context.output; 13 | output.append(getStart(context)).append("\n "); 14 | for (int i = 0; i < node.labels.size(); ++i) { 15 | output.append(String.format(" %s\n ", getPart(context, node.keys.get(i), node.labels.get(i).getLabel()))); 16 | } 17 | output.append(String.format(" %s\n ", getDefault(context, node.dflt.getLabel()))); 18 | this.instructionName = "LOOKUPSWITCH_END"; 19 | } 20 | 21 | private static String getStart(final MethodContext context) { 22 | return context.getSnippets().getSnippet("LOOKUPSWITCH_START", Util.createMap("stackindexm1", String.valueOf(context.stackPointer - 1))); 23 | } 24 | 25 | private static String getPart(final MethodContext context, final int key, final Label label) { 26 | return context.getSnippets().getSnippet("LOOKUPSWITCH_PART", Util.createMap("key", key, "label", context.getLabelPool().getName(label))); 27 | } 28 | 29 | private static String getDefault(final MethodContext context, final Label label) { 30 | return context.getSnippets().getSnippet("LOOKUPSWITCH_DEFAULT", Util.createMap("label", context.getLabelPool().getName(label))); 31 | } 32 | 33 | @Override 34 | public String insnToString(final MethodContext context, final LookupSwitchInsnNode node) { 35 | return "LOOKUPSWITCH"; 36 | } 37 | 38 | @Override 39 | public int getNewStackPointer(final LookupSwitchInsnNode node, final int currentStackPointer) { 40 | return currentStackPointer - 1; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/CatchesBlock.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.objectweb.asm.tree.LabelNode; 4 | 5 | import java.util.List; 6 | import java.util.Objects; 7 | 8 | public class CatchesBlock 9 | { 10 | private final List catches; 11 | 12 | public CatchesBlock(final List catches) { 13 | this.catches = catches; 14 | } 15 | 16 | public List getCatches() { 17 | return this.catches; 18 | } 19 | 20 | @Override 21 | public boolean equals(final Object o) { 22 | if (this == o) { 23 | return true; 24 | } 25 | if (o == null || this.getClass() != o.getClass()) { 26 | return false; 27 | } 28 | final CatchesBlock that = (CatchesBlock)o; 29 | return Objects.equals(this.catches, that.catches); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(this.catches); 35 | } 36 | 37 | public static class CatchBlock 38 | { 39 | private final String clazz; 40 | private final LabelNode handler; 41 | 42 | public CatchBlock(final String clazz, final LabelNode handler) { 43 | this.clazz = clazz; 44 | this.handler = handler; 45 | } 46 | 47 | public String getClazz() { 48 | return this.clazz; 49 | } 50 | 51 | public LabelNode getHandler() { 52 | return this.handler; 53 | } 54 | 55 | @Override 56 | public boolean equals(final Object o) { 57 | if (this == o) { 58 | return true; 59 | } 60 | if (o == null || this.getClass() != o.getClass()) { 61 | return false; 62 | } 63 | final CatchBlock that = (CatchBlock)o; 64 | return Objects.equals(this.clazz, that.clazz) && Objects.equals(this.handler, that.handler); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(this.clazz, this.handler); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/EncodeUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | public class EncodeUtils 7 | { 8 | public static final String UTF_8 = "UTF-8"; 9 | private static final char[] BASE62; 10 | 11 | public static String encodeHex(final byte[] input) { 12 | return HexUtil.encodeToString(input); 13 | } 14 | 15 | public static byte[] decodeHex(final String input) { 16 | return HexUtil.decode(input.getBytes(StandardCharsets.UTF_8)); 17 | } 18 | 19 | public static String encodeBase64(final byte[] input) { 20 | return Base64Util.encrypt(input); 21 | } 22 | 23 | public static String encodeBase64(final String input) { 24 | if (StringUtils.isBlank(input)) { 25 | return ""; 26 | } 27 | try { 28 | return Base64Util.encrypt(input.getBytes("UTF-8")); 29 | } 30 | catch (UnsupportedEncodingException e) { 31 | return ""; 32 | } 33 | } 34 | 35 | public static byte[] decodeBase64(final String input) { 36 | try { 37 | return Base64Util.decrypt(input); 38 | } 39 | catch (Exception e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | 44 | public static String decodeBase64String(final String input) { 45 | if (StringUtils.isBlank(input)) { 46 | return ""; 47 | } 48 | try { 49 | return new String(Base64Util.decrypt(input), "UTF-8"); 50 | } 51 | catch (Exception e) { 52 | return ""; 53 | } 54 | } 55 | 56 | public static String encodeBase62(final byte[] input) { 57 | final char[] chars = new char[input.length]; 58 | for (int i = 0; i < input.length; ++i) { 59 | chars[i] = EncodeUtils.BASE62[(input[i] & 0xFF) % EncodeUtils.BASE62.length]; 60 | } 61 | return new String(chars); 62 | } 63 | 64 | static { 65 | BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/InterfaceStaticClassProvider.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.objectweb.asm.tree.ClassNode; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class InterfaceStaticClassProvider 10 | { 11 | private final String nativeDir; 12 | private final List readyClasses; 13 | private ClassNode currentClass; 14 | private StringBuilder methods; 15 | 16 | public InterfaceStaticClassProvider(final String nativeDir) { 17 | this.nativeDir = nativeDir; 18 | this.readyClasses = new ArrayList(); 19 | } 20 | 21 | public void addMethod(final MethodNode method, final String source) { 22 | final ClassNode classNode = this.getCurrentClass(); 23 | if (classNode.methods.size() > 16384) { 24 | throw new RuntimeException("too many static interface methods"); 25 | } 26 | classNode.methods.add(method); 27 | this.methods.append(source); 28 | } 29 | 30 | public void newClass() { 31 | this.currentClass = null; 32 | this.methods = null; 33 | } 34 | 35 | public List getReadyClasses() { 36 | return this.readyClasses; 37 | } 38 | 39 | public boolean isEmpty() { 40 | return this.currentClass == null; 41 | } 42 | 43 | public String getMethods() { 44 | return this.methods.toString(); 45 | } 46 | 47 | public String getCurrentClassName() { 48 | return this.getCurrentClass().name; 49 | } 50 | 51 | private ClassNode getCurrentClass() { 52 | if (this.currentClass == null) { 53 | this.methods = new StringBuilder(); 54 | this.currentClass = new ClassNode(); 55 | this.currentClass.version = 52; 56 | this.currentClass.sourceFile = "synthetic"; 57 | this.currentClass.access = 1; 58 | this.currentClass.superName = "java/lang/Object"; 59 | this.currentClass.name = this.nativeDir + "/interfacestatic/Methods" + this.readyClasses.size(); 60 | this.readyClasses.add(this.currentClass); 61 | } 62 | return this.currentClass; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/CryptoUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import javax.crypto.KeyGenerator; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.SecretKeyFactory; 6 | import javax.crypto.spec.PBEKeySpec; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.SecureRandom; 10 | import java.security.spec.InvalidKeySpecException; 11 | import java.security.spec.KeySpec; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class CryptoUtils 16 | { 17 | public static String hex(final byte[] bytes) { 18 | final StringBuilder result = new StringBuilder(); 19 | for (final byte b : bytes) { 20 | result.append(String.format("%02x", b)); 21 | } 22 | return result.toString(); 23 | } 24 | 25 | public static String hexWithBlockSize(final byte[] bytes, int blockSize) { 26 | final String hex = hex(bytes); 27 | blockSize *= 2; 28 | final List result = new ArrayList(); 29 | for (int index = 0; index < hex.length(); index += blockSize) { 30 | result.add(hex.substring(index, Math.min(index + blockSize, hex.length()))); 31 | } 32 | return result.toString(); 33 | } 34 | 35 | public static byte[] getRandomNonce(final int numBytes) { 36 | final byte[] nonce = new byte[numBytes]; 37 | new SecureRandom().nextBytes(nonce); 38 | return nonce; 39 | } 40 | 41 | public static SecretKey getAESKey(final int keysize) throws NoSuchAlgorithmException { 42 | final KeyGenerator keyGen = KeyGenerator.getInstance("AES"); 43 | keyGen.init(keysize, SecureRandom.getInstanceStrong()); 44 | return keyGen.generateKey(); 45 | } 46 | 47 | public static SecretKey getAESKeyFromPassword(final char[] password, final byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { 48 | final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); 49 | final KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); 50 | final SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); 51 | return secret; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/ClassNodeCache.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ClassNodeCache 7 | { 8 | private final String pointerPattern; 9 | private final Map cache; 10 | 11 | public ClassNodeCache(final String pointerPattern) { 12 | this.pointerPattern = pointerPattern; 13 | this.cache = new HashMap(); 14 | } 15 | 16 | public String getPointer(final String key) { 17 | return String.format(this.pointerPattern, this.getId(key)); 18 | } 19 | 20 | public int getId(String clazz) { 21 | if (clazz.endsWith(";") && !clazz.startsWith("[")) { 22 | if (clazz.startsWith("L")) { 23 | clazz = clazz.substring(1); 24 | } 25 | clazz = clazz.replace(";", ""); 26 | } 27 | if (clazz.startsWith("native/magic/1/linkcallsite/obfuscator")) { 28 | return 0; 29 | } 30 | if (!this.cache.containsKey(clazz)) { 31 | final CachedClassInfo classInfo = new CachedClassInfo(clazz, clazz, "", this.cache.size(), false); 32 | this.cache.put(clazz, classInfo); 33 | } 34 | return this.cache.get(clazz).getId(); 35 | } 36 | 37 | public CachedClassInfo getClass(String clazz) { 38 | if (clazz.endsWith(";") && !clazz.startsWith("[")) { 39 | if (clazz.startsWith("L")) { 40 | clazz = clazz.substring(1); 41 | } 42 | clazz = clazz.replace(";", ""); 43 | } 44 | if (clazz.startsWith("native/magic/1/linkcallsite/obfuscator")) { 45 | return null; 46 | } 47 | if (!this.cache.containsKey(clazz)) { 48 | final CachedClassInfo classInfo = new CachedClassInfo(clazz, clazz, "", this.cache.size(), false); 49 | this.cache.put(clazz, classInfo); 50 | } 51 | return this.cache.get(clazz); 52 | } 53 | 54 | public int size() { 55 | return this.cache.size(); 56 | } 57 | 58 | public boolean isEmpty() { 59 | return this.cache.isEmpty(); 60 | } 61 | 62 | public Map getCache() { 63 | return this.cache; 64 | } 65 | 66 | public void clear() { 67 | this.cache.clear(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/TamperUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | import org.objectweb.asm.tree.AbstractInsnNode; 6 | import org.objectweb.asm.tree.LdcInsnNode; 7 | import org.objectweb.asm.tree.MethodNode; 8 | public class TamperUtils { 9 | private static final char[] ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789".toCharArray(); 10 | 11 | public static String encrypt(String msg, String className, String methodName, int cpSize) { 12 | int key1 = className.hashCode(); 13 | int key2 = methodName.hashCode(); 14 | char[] chars = msg.toCharArray(); 15 | char[] encrypted = new char[chars.length]; 16 | 17 | for (int i = 0; i < encrypted.length; i++) { 18 | switch (i % 2) { 19 | case 0: 20 | encrypted[i] = (char) (cpSize ^ key1 ^ chars[i]); 21 | break; 22 | case 1: 23 | encrypted[i] = (char) (cpSize ^ key2 ^ chars[i]); 24 | break; 25 | } 26 | } 27 | 28 | return new String(encrypted); 29 | } 30 | 31 | public static boolean hasInstructions(MethodNode methodNode) { 32 | return methodNode.instructions != null && methodNode.instructions.size() != 0; 33 | } 34 | 35 | public static boolean isString(AbstractInsnNode insn) { 36 | return insn instanceof LdcInsnNode && ((LdcInsnNode) insn).cst instanceof String; 37 | } 38 | 39 | public static String randomClassName(List classNames) { 40 | String first = classNames.get(randomInt(classNames.size())); 41 | String second = classNames.get(randomInt(classNames.size())); 42 | 43 | return first + '$' + second.substring(second.lastIndexOf("/") + 1); 44 | } 45 | 46 | public static String randomString() { 47 | int length = randomInt(8) + 8; 48 | StringBuilder sb = new StringBuilder(); 49 | 50 | for (int i = 0; i < length; i++) { 51 | sb.append(ALPHA[randomInt(ALPHA.length)]); 52 | } 53 | 54 | return sb.toString(); 55 | } 56 | 57 | public static int randomInt(int bounds) { 58 | return ThreadLocalRandom.current().nextInt(bounds); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/NativeSignature.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic; 2 | 3 | import java.util.Iterator; 4 | import java.util.stream.IntStream; 5 | 6 | /** 7 | * 此类用于处理Java方法名或类名,生成JNI兼容的名称。 8 | */ 9 | public class NativeSignature { 10 | 11 | /** 12 | * 将给定的方法名转换为JNI定义格式。保留有效的JNI标识符字符(小写字母、大写字母、下划线和数字), 13 | * 其他字符替换为下划线。 14 | * 15 | * @param name 原始Java方法名 16 | * @param sb StringBuilder对象,用于构建JNI定义格式的名称 17 | */ 18 | public static void getJNIDefineName(String name, StringBuilder sb) { 19 | for (char c : name.toCharArray()) { 20 | if ((c >= 'a' && c <= 'z') || 21 | (c >= 'A' && c <= 'Z') || 22 | c == '_' || 23 | (c >= '0' && c <= '9')) { 24 | sb.append(c); 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * 将给定的Java名称转换为JNI兼容格式。将特殊字符转换为下划线及十六进制编码。 31 | * 32 | * @param name 原始Java方法名或类名 33 | * @param sb StringBuilder对象,用于构建JNI兼容格式的名称 34 | */ 35 | public static void getJNICompatibleName(String name, StringBuilder sb) { 36 | for (char c : name.toCharArray()) { 37 | if (c < 127) { 38 | switch (c) { 39 | case '.': 40 | case '/': 41 | sb.append('_'); 42 | break; 43 | case '$': 44 | sb.append("_00024"); 45 | break; 46 | case '_': 47 | sb.append("_1"); 48 | break; 49 | case ';': 50 | sb.append("_2"); 51 | break; 52 | case '[': 53 | sb.append("_3"); 54 | break; 55 | default: 56 | sb.append(c); 57 | } 58 | } else { 59 | // 对于非ASCII字符,使用下划线和其Unicode编码的十六进制表示 60 | sb.append("_0"); 61 | sb.append(String.format("%04x", (int) c)); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * 获取一个给定Java名称的JNI兼容名称。 68 | * 69 | * @param name 原始Java方法名或类名 70 | * @return 返回JNI兼容格式的名称 71 | */ 72 | public static String getJNICompatibleName(String name) { 73 | StringBuilder sb = new StringBuilder(); 74 | NativeSignature.getJNICompatibleName(name, sb); 75 | return sb.toString(); 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/FieldHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import cc.nuym.jnic.cache.CachedClassInfo; 6 | import cc.nuym.jnic.cache.CachedFieldInfo; 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.Type; 9 | import org.objectweb.asm.tree.FieldInsnNode; 10 | 11 | import java.util.List; 12 | 13 | public class FieldHandler extends GenericInstructionHandler 14 | { 15 | @Override 16 | protected void process(final MethodContext context, final FieldInsnNode node) { 17 | boolean isStatic = node.getOpcode() == Opcodes.GETSTATIC || node.getOpcode() == Opcodes.PUTSTATIC; 18 | CachedFieldInfo info = new CachedFieldInfo(node.owner, node.name, node.desc, isStatic); 19 | 20 | instructionName += "_" + Type.getType(node.desc).getSort(); 21 | 22 | this.props.put("class_ptr", "c_" + context.getCachedClasses().getId(node.owner) + "_"); 23 | final CachedClassInfo classInfo = context.getCachedClasses().getCache().get(node.owner); 24 | final List cachedFields = classInfo.getCachedFields(); 25 | for (int i = 0; i < cachedFields.size(); ++i) { 26 | final CachedFieldInfo fieldNode = cachedFields.get(i); 27 | if (fieldNode.getName().equals(node.name)) { 28 | this.props.put("field_id", "id_" + i); 29 | } 30 | } 31 | if (this.props.get("field_id") == null) { 32 | cachedFields.add(info); 33 | this.props.put("field_id", "id_" + (cachedFields.size() - 1)); 34 | } 35 | } 36 | 37 | @Override 38 | public String insnToString(MethodContext context, FieldInsnNode node) { 39 | return String.format("%s %s.%s %s", Util.getOpcodeString(node.getOpcode()), node.owner, node.name, node.desc); 40 | } 41 | 42 | @Override 43 | public int getNewStackPointer(FieldInsnNode node, int currentStackPointer) { 44 | if (node.getOpcode() == Opcodes.GETFIELD || node.getOpcode() == Opcodes.PUTFIELD) { 45 | currentStackPointer -= 1; 46 | } 47 | if (node.getOpcode() == Opcodes.GETSTATIC || node.getOpcode() == Opcodes.GETFIELD) { 48 | currentStackPointer += Type.getType(node.desc).getSize(); 49 | } 50 | if (node.getOpcode() == Opcodes.PUTSTATIC || node.getOpcode() == Opcodes.PUTFIELD) { 51 | currentStackPointer -= Type.getType(node.desc).getSize(); 52 | } 53 | return currentStackPointer; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/BootstrapMethodsPool.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.utils; 3 | 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.objectweb.asm.tree.MethodNode; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.function.Consumer; 10 | 11 | public class BootstrapMethodsPool { 12 | private final String baseName; 13 | private final HashMap namePool = new HashMap(); 14 | private final HashMap> methods = new HashMap(); 15 | private final List classes = new ArrayList(); 16 | 17 | public BootstrapMethodsPool(String baseName) { 18 | this.baseName = baseName; 19 | } 20 | 21 | public BootstrapMethod getMethod(String name, String desc, Consumer creator) { 22 | ClassNode classNode = null; 23 | BootstrapMethod existingMethod = (BootstrapMethod)this.methods.computeIfAbsent(name, unused -> new HashMap()).get(desc); 24 | if (existingMethod != null) { 25 | return existingMethod; 26 | } 27 | MethodNode newMethod = new MethodNode(4169, name, desc, null, new String[0]); 28 | creator.accept(newMethod); 29 | ClassNode classNode2 = this.classes.size() == 0 ? null : (classNode = this.classes.get((int)0).methods.size() > 10000 ? null : this.classes.get(0)); 30 | if (classNode == null) { 31 | classNode = new ClassNode(458752); 32 | classNode.version = 52; 33 | classNode.name = this.baseName + "/Bootstrap" + this.classes.size(); 34 | this.classes.add(classNode); 35 | } 36 | classNode.methods.add(newMethod); 37 | BootstrapMethod bootstrapMethod = new BootstrapMethod(classNode, newMethod); 38 | this.methods.computeIfAbsent(name, unused -> new HashMap()).put(desc, bootstrapMethod); 39 | return bootstrapMethod; 40 | } 41 | 42 | public List getClasses() { 43 | return this.classes; 44 | } 45 | 46 | public static class BootstrapMethod { 47 | private final ClassNode classNode; 48 | private final MethodNode methodNode; 49 | 50 | private BootstrapMethod(ClassNode classNode, MethodNode methodNode) { 51 | this.classNode = classNode; 52 | this.methodNode = methodNode; 53 | } 54 | 55 | public ClassNode getClassNode() { 56 | return this.classNode; 57 | } 58 | 59 | public MethodNode getMethodNode() { 60 | return this.methodNode; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/MethodContext.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import cc.nuym.jnic.NativeObfuscator; 4 | import cc.nuym.jnic.cache.ClassNodeCache; 5 | import cc.nuym.jnic.cache.FieldNodeCache; 6 | import cc.nuym.jnic.cache.MethodNodeCache; 7 | import cc.nuym.jnic.cache.NodeCache; 8 | import org.objectweb.asm.Type; 9 | import org.objectweb.asm.tree.ClassNode; 10 | import org.objectweb.asm.tree.MethodNode; 11 | import org.objectweb.asm.tree.TryCatchBlockNode; 12 | 13 | import java.util.*; 14 | 15 | public class MethodContext 16 | { 17 | public NativeObfuscator obfuscator; 18 | public final MethodNode method; 19 | public final ClassNode clazz; 20 | public final int methodIndex; 21 | public final int classIndex; 22 | public final StringBuilder output; 23 | public final StringBuilder nativeMethods; 24 | public Type ret; 25 | public ArrayList argTypes; 26 | public int line; 27 | public List stack; 28 | public List locals; 29 | public Set tryCatches; 30 | public Map catches; 31 | public MethodNode proxyMethod; 32 | public MethodNode nativeMethod; 33 | public int stackPointer; 34 | private final LabelPool labelPool; 35 | 36 | public MethodContext(final NativeObfuscator obfuscator, final MethodNode method, final int methodIndex, final ClassNode clazz, final int classIndex) { 37 | this.labelPool = new LabelPool(); 38 | this.obfuscator = obfuscator; 39 | this.method = method; 40 | this.methodIndex = methodIndex; 41 | this.clazz = clazz; 42 | this.classIndex = classIndex; 43 | this.output = new StringBuilder(); 44 | this.nativeMethods = new StringBuilder(); 45 | this.line = -1; 46 | this.stack = new ArrayList(); 47 | this.locals = new ArrayList(); 48 | this.tryCatches = new HashSet(); 49 | this.catches = new HashMap(); 50 | } 51 | 52 | public NodeCache getCachedStrings() { 53 | return this.obfuscator.getCachedStrings(); 54 | } 55 | 56 | public ClassNodeCache getCachedClasses() { 57 | return this.obfuscator.getCachedClasses(); 58 | } 59 | 60 | public MethodNodeCache getCachedMethods() { 61 | return this.obfuscator.getCachedMethods(); 62 | } 63 | 64 | public FieldNodeCache getCachedFields() { 65 | return this.obfuscator.getCachedFields(); 66 | } 67 | 68 | public Snippets getSnippets() { 69 | return this.obfuscator.getSnippets(); 70 | } 71 | 72 | public LabelPool getLabelPool() { 73 | return this.labelPool; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/IOUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import java.io.*; 4 | import java.nio.charset.Charset; 5 | 6 | public final class IOUtils 7 | { 8 | public static final int DEFAULT_BUFFER_SIZE = 8192; 9 | 10 | public static byte[] readFullyWithoutClosing(final InputStream stream) throws IOException { 11 | final ByteArrayOutputStream result = new ByteArrayOutputStream(); 12 | copyTo(stream, result); 13 | return result.toByteArray(); 14 | } 15 | 16 | public static ByteArrayOutputStream readFully(final InputStream stream) throws IOException { 17 | Throwable t = null; 18 | try { 19 | final ByteArrayOutputStream result = new ByteArrayOutputStream(); 20 | copyTo(stream, result); 21 | return result; 22 | } 23 | catch (Throwable t2) { 24 | t = t2; 25 | throw t2; 26 | } 27 | finally { 28 | if (stream != null) { 29 | if (t != null) { 30 | try { 31 | stream.close(); 32 | } 33 | catch (Throwable t3) { 34 | t.addSuppressed(t3); 35 | } 36 | } 37 | else { 38 | stream.close(); 39 | } 40 | } 41 | } 42 | } 43 | 44 | public static byte[] readFullyAsByteArray(final InputStream stream) throws IOException { 45 | return readFully(stream).toByteArray(); 46 | } 47 | 48 | public static String readFullyAsString(final InputStream stream) throws IOException { 49 | return readFully(stream).toString("UTF-8"); 50 | } 51 | 52 | public static String readFullyAsString(final InputStream stream, final Charset charset) throws IOException { 53 | return readFully(stream).toString(charset.name()); 54 | } 55 | 56 | public static void write(final String text, final OutputStream outputStream) throws IOException { 57 | write(text.getBytes(), outputStream); 58 | } 59 | 60 | public static void write(final byte[] bytes, final OutputStream outputStream) throws IOException { 61 | copyTo(new ByteArrayInputStream(bytes), outputStream); 62 | } 63 | 64 | public static void copyTo(final InputStream src, final OutputStream dest) throws IOException { 65 | copyTo(src, dest, new byte[8192]); 66 | } 67 | 68 | public static void copyTo(final InputStream src, final OutputStream dest, final byte[] buf) throws IOException { 69 | while (true) { 70 | final int len = src.read(buf); 71 | if (len == -1) { 72 | break; 73 | } 74 | dest.write(buf, 0, len); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/special/ClInitSpecialMethodProcessor.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.special; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.tree.*; 8 | 9 | public class ClInitSpecialMethodProcessor implements SpecialMethodProcessor { 10 | 11 | @Override 12 | public String preprocess(MethodContext context) { 13 | final String proxyMethodName = "$jnicClinit"; 14 | if (!Util.hasFlag(context.clazz.access, Opcodes.ACC_INTERFACE)) { 15 | MethodNode proxyMethod = new MethodNode( 16 | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, 17 | proxyMethodName, 18 | context.method.desc, 19 | context.method.signature, 20 | new String[0]); 21 | context.clazz.methods.add(proxyMethod); 22 | context.proxyMethod = proxyMethod; 23 | } 24 | return proxyMethodName; 25 | } 26 | 27 | @Override 28 | public void postprocess(MethodContext context) { 29 | InsnList instructions = context.method.instructions; 30 | instructions.clear(); 31 | 32 | instructions.add(new LdcInsnNode(context.classIndex)); 33 | instructions.add(new LdcInsnNode(Type.getObjectType(context.clazz.name))); 34 | 35 | // 添加调用JNICLoader.registerNativesForClass方法的指令 36 | instructions.add(new MethodInsnNode( 37 | Opcodes.INVOKESTATIC, 38 | context.obfuscator.getNativeDir() + "/JNICLoader", 39 | "registerNativesForClass", 40 | "(ILjava/lang/Class;)V", 41 | false)); 42 | 43 | instructions.add(new MethodInsnNode( 44 | Opcodes.INVOKESTATIC, 45 | context.clazz.name, 46 | "$jnicLoader", 47 | "()V", 48 | false)); 49 | 50 | if (Util.hasFlag(context.clazz.access, Opcodes.ACC_INTERFACE)) { 51 | if (context.nativeMethod == null) { 52 | throw new RuntimeException("Native method not created?!"); 53 | } 54 | instructions.add(new MethodInsnNode( 55 | Opcodes.INVOKESTATIC, 56 | context.obfuscator.getStaticClassProvider().getCurrentClassName(), 57 | context.nativeMethod.name, 58 | context.nativeMethod.desc, 59 | false)); 60 | } else if (!context.obfuscator.getNoInitClassMap().containsKey(context.clazz.name)) { 61 | instructions.add(new MethodInsnNode( 62 | Opcodes.INVOKESTATIC, 63 | context.clazz.name, 64 | "$jnicClinit", 65 | context.method.desc, 66 | false)); 67 | } 68 | 69 | instructions.add(new InsnNode(Opcodes.RETURN)); // 使用RETURN常量替代177 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/special/DefaultSpecialMethodProcessor.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.special; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.tree.*; 8 | 9 | public class DefaultSpecialMethodProcessor implements SpecialMethodProcessor { 10 | 11 | @Override 12 | public String preprocess(MethodContext context) { 13 | context.proxyMethod = context.method; 14 | MethodNode method = context.method; 15 | // 添加ACC_NATIVE标志,表示此方法是原生方法 16 | method.access |= Opcodes.ACC_NATIVE; 17 | return "native_" + method.name + context.methodIndex; 18 | } 19 | 20 | @Override 21 | 22 | public void postprocess(MethodContext context) { 23 | 24 | context.method.instructions.clear(); 25 | 26 | 27 | if (Util.hasFlag(context.clazz.access, Opcodes.ACC_INTERFACE)) { 28 | 29 | InsnList instructions = new InsnList(); 30 | 31 | int localVarsPosition = 0; 32 | 33 | 34 | for (Type arg : context.argTypes) { 35 | 36 | instructions.add(new VarInsnNode(arg.getOpcode(Opcodes.ILOAD), localVarsPosition)); 37 | 38 | localVarsPosition += arg.getSize(); 39 | } 40 | 41 | if (context.nativeMethod == null) { 42 | throw new RuntimeException("Native method not created?!"); 43 | } 44 | // 调用原生方法的指令 45 | 46 | instructions.add(new MethodInsnNode( 47 | 48 | Opcodes.INVOKESTATIC, 49 | 50 | context.obfuscator.getStaticClassProvider().getCurrentClassName(), 51 | 52 | context.nativeMethod.name, 53 | 54 | context.nativeMethod.desc, 55 | 56 | false)); 57 | 58 | 59 | Type returnType = Type.getReturnType(context.method.desc); 60 | 61 | // 根据返回类型添加合适的返回指令 62 | 63 | switch (returnType.getSort()) { 64 | case Type.VOID: 65 | instructions.add(new InsnNode(Opcodes.RETURN)); 66 | break; 67 | 68 | case Type.BOOLEAN: 69 | case Type.BYTE: 70 | case Type.CHAR: 71 | case Type.SHORT: 72 | case Type.INT: 73 | instructions.add(new InsnNode(Opcodes.IRETURN)); 74 | break; 75 | 76 | case Type.LONG: 77 | instructions.add(new InsnNode(Opcodes.LRETURN)); 78 | break; 79 | 80 | case Type.FLOAT: 81 | instructions.add(new InsnNode(Opcodes.FRETURN)); 82 | break; 83 | 84 | case Type.DOUBLE: 85 | instructions.add(new InsnNode(Opcodes.DRETURN)); 86 | break; 87 | 88 | case Type.OBJECT: 89 | case Type.ARRAY: 90 | instructions.add(new InsnNode(Opcodes.ARETURN)); 91 | break; 92 | 93 | } 94 | context.method.instructions = instructions; 95 | 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/HexUtil.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import java.nio.charset.Charset; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | public class HexUtil 7 | { 8 | public static final Charset DEFAULT_CHARSET; 9 | private static final byte[] DIGITS_LOWER; 10 | private static final byte[] DIGITS_UPPER; 11 | 12 | public static byte[] encode(final byte[] data) { 13 | return encode(data, true); 14 | } 15 | 16 | public static byte[] encode(final byte[] data, final boolean toLowerCase) { 17 | return encode(data, toLowerCase ? HexUtil.DIGITS_LOWER : HexUtil.DIGITS_UPPER); 18 | } 19 | 20 | private static byte[] encode(final byte[] data, final byte[] digits) { 21 | final int len = data.length; 22 | final byte[] out = new byte[len << 1]; 23 | int i = 0; 24 | int j = 0; 25 | while (i < len) { 26 | out[j++] = digits[(0xF0 & data[i]) >>> 4]; 27 | out[j++] = digits[0xF & data[i]]; 28 | ++i; 29 | } 30 | return out; 31 | } 32 | 33 | public static String encodeToString(final byte[] data, final boolean toLowerCase) { 34 | return new String(encode(data, toLowerCase), HexUtil.DEFAULT_CHARSET); 35 | } 36 | 37 | public static String encodeToString(final byte[] data) { 38 | return new String(encode(data), HexUtil.DEFAULT_CHARSET); 39 | } 40 | 41 | public static String encodeToString(final String data) { 42 | if (StringUtils.isBlank(data)) { 43 | return null; 44 | } 45 | return encodeToString(data.getBytes(HexUtil.DEFAULT_CHARSET)); 46 | } 47 | 48 | public static byte[] decode(final String data) { 49 | if (StringUtils.isBlank(data)) { 50 | return null; 51 | } 52 | return decode(data.getBytes(HexUtil.DEFAULT_CHARSET)); 53 | } 54 | 55 | public static String decodeToString(final byte[] data) { 56 | final byte[] decodeBytes = decode(data); 57 | return new String(decodeBytes, HexUtil.DEFAULT_CHARSET); 58 | } 59 | 60 | public static String decodeToString(final String data) { 61 | if (StringUtils.isBlank(data)) { 62 | return null; 63 | } 64 | return decodeToString(data.getBytes(HexUtil.DEFAULT_CHARSET)); 65 | } 66 | 67 | public static byte[] decode(final byte[] data) { 68 | final int len = data.length; 69 | if ((len & 0x1) != 0x0) { 70 | throw new IllegalArgumentException("hexBinary needs to be even-length: " + len); 71 | } 72 | final byte[] out = new byte[len >> 1]; 73 | int f; 74 | for (int i = 0, j = 0; j < len; ++j, f |= toDigit(data[j], j), ++j, out[i] = (byte)(f & 0xFF), ++i) { 75 | f = toDigit(data[j], j) << 4; 76 | } 77 | return out; 78 | } 79 | 80 | private static int toDigit(final byte b, final int index) { 81 | final int digit = Character.digit(b, 16); 82 | if (digit == -1) { 83 | throw new IllegalArgumentException("Illegal hexadecimal byte " + b + " at index " + index); 84 | } 85 | return digit; 86 | } 87 | 88 | static { 89 | DEFAULT_CHARSET = StandardCharsets.UTF_8; 90 | DIGITS_LOWER = new byte[] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102 }; 91 | DIGITS_UPPER = new byte[] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij+all 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### Intellij+all Patch ### 84 | # Ignore everything but code style settings and run configurations 85 | # that are supposed to be shared within teams. 86 | 87 | .idea/* 88 | 89 | !.idea/codeStyles 90 | !.idea/runConfigurations 91 | 92 | ### Java ### 93 | # Compiled class file 94 | *.class 95 | 96 | # Log file 97 | *.log 98 | 99 | # BlueJ files 100 | *.ctxt 101 | 102 | # Mobile Tools for Java (J2ME) 103 | .mtj.tmp/ 104 | 105 | # Package Files # 106 | !libs/*.jar 107 | *.jar 108 | *.war 109 | *.nar 110 | *.ear 111 | #*.zip 112 | *.tar.gz 113 | *.rar 114 | 115 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 116 | hs_err_pid* 117 | replay_pid* 118 | 119 | ### Gradle ### 120 | .gradle 121 | **/build/ 122 | !src/**/build/ 123 | 124 | # Ignore Gradle GUI config 125 | gradle-app.setting 126 | 127 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 128 | !gradle-wrapper.jar 129 | 130 | # Avoid ignore Gradle wrappper properties 131 | !gradle-wrapper.properties 132 | 133 | # Cache of project 134 | .gradletasknamecache 135 | 136 | # Eclipse Gradle plugin generated files 137 | # Eclipse Core 138 | .project 139 | # JDT-specific (Eclipse Java Development Tools) 140 | .classpath 141 | 142 | ### Gradle Patch ### 143 | # Java heap dump 144 | *.hprof 145 | 146 | # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij+all -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/LdcHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Type; 6 | import org.objectweb.asm.tree.LdcInsnNode; 7 | 8 | public class LdcHandler extends GenericInstructionHandler 9 | { 10 | public static String getIntString(final int value) { 11 | return (value == Integer.MIN_VALUE) ? "(jint) 2147483648U" : String.valueOf(value); 12 | } 13 | 14 | public static String getLongValue(final long value) { 15 | return (value == Long.MIN_VALUE) ? "(jlong) 9223372036854775808ULL" : (String.valueOf(value) + "LL"); 16 | } 17 | 18 | public static String getFloatValue(final float value) { 19 | if (Float.isNaN(value)) { 20 | return "NAN"; 21 | } 22 | if (value == Float.POSITIVE_INFINITY) { 23 | return "HUGE_VALF"; 24 | } 25 | if (value == Float.NEGATIVE_INFINITY) { 26 | return "-HUGE_VALF"; 27 | } 28 | return value + "f"; 29 | } 30 | 31 | public static String getDoubleValue(final double value) { 32 | if (Double.isNaN(value)) { 33 | return "NAN"; 34 | } 35 | if (value == Double.POSITIVE_INFINITY) { 36 | return "HUGE_VAL"; 37 | } 38 | if (value == Double.NEGATIVE_INFINITY) { 39 | return "-HUGE_VAL"; 40 | } 41 | return String.valueOf(value); 42 | } 43 | 44 | @Override 45 | protected void process(final MethodContext context, final LdcInsnNode node) { 46 | final Object cst = node.cst; 47 | if (cst instanceof String) { 48 | if (node.cst.toString().length() > 0) { 49 | this.instructionName += "_STRING"; 50 | this.props.put("cst_ptr", Util.utf82unicode(node.cst.toString())); 51 | this.props.put("cst_length", "" + node.cst.toString().length()); 52 | } 53 | else { 54 | this.instructionName += "_STRING_NULL"; 55 | } 56 | } 57 | else if (cst instanceof Integer) { 58 | this.instructionName += "_INT"; 59 | this.props.put("cst", getIntString((int)cst)); 60 | } 61 | else if (cst instanceof Long) { 62 | this.instructionName += "_LONG"; 63 | this.props.put("cst", getLongValue((long)cst)); 64 | } 65 | else if (cst instanceof Float) { 66 | this.instructionName += "_FLOAT"; 67 | this.props.put("cst", getFloatValue((float)node.cst)); 68 | } 69 | else if (cst instanceof Double) { 70 | this.instructionName += "_DOUBLE"; 71 | this.props.put("cst", getDoubleValue((double)node.cst)); 72 | } 73 | else { 74 | if (!(cst instanceof Type)) { 75 | throw new UnsupportedOperationException(); 76 | } 77 | this.instructionName += "_CLASS"; 78 | this.props.put("class_ptr", "c_" + context.getCachedClasses().getId(node.cst.toString()) + "_"); 79 | this.props.put("cst_ptr", context.getCachedClasses().getPointer(node.cst.toString())); 80 | } 81 | } 82 | 83 | @Override 84 | public String insnToString(final MethodContext context, final LdcInsnNode node) { 85 | return String.format("LDC %s", node.cst); 86 | } 87 | 88 | @Override 89 | public int getNewStackPointer(final LdcInsnNode node, final int currentStackPointer) { 90 | if (node.cst instanceof Double || node.cst instanceof Long) { 91 | return currentStackPointer + 2; 92 | } 93 | return currentStackPointer + 1; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/helpers/ProcessHelper.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.helpers; 3 | 4 | import cc.nuym.jnic.env.SetupManager; 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.UncheckedIOException; 10 | import java.nio.file.Path; 11 | import java.util.List; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.function.Consumer; 16 | 17 | public class ProcessHelper { 18 | private static final ExecutorService executor = Executors.newFixedThreadPool(2); 19 | 20 | private static void readStream(InputStream is, Consumer consumer) { 21 | executor.submit(() -> { 22 | try (InputStreamReader isr = new InputStreamReader(is); 23 | BufferedReader reader = new BufferedReader(isr);){ 24 | int count; 25 | char[] buf = new char[1024]; 26 | while ((count = reader.read(buf)) != -1) { 27 | consumer.accept(String.copyValueOf(buf, 0, count)); 28 | } 29 | } 30 | catch (IOException e) { 31 | throw new UncheckedIOException(e); 32 | } 33 | }); 34 | } 35 | 36 | public static ProcessResult run(Path directory, long timeLimit, List command) throws IOException { 37 | Process process = new ProcessBuilder(command).directory(directory.toFile()).start(); 38 | long startTime = System.currentTimeMillis(); 39 | ProcessResult result = new ProcessResult(); 40 | result.commandLine = String.join((CharSequence)" ", command); 41 | StringBuilder stdoutBuilder = new StringBuilder(); 42 | StringBuilder stderrBuilder = new StringBuilder(); 43 | ProcessHelper.readStream(process.getInputStream(), stdoutBuilder::append); 44 | ProcessHelper.readStream(process.getErrorStream(), stderrBuilder::append); 45 | try { 46 | if (!process.waitFor(timeLimit, TimeUnit.MILLISECONDS)) { 47 | result.timeout = true; 48 | process.destroyForcibly(); 49 | } 50 | process.waitFor(); 51 | } 52 | catch (InterruptedException interruptedException) { 53 | // empty catch block 54 | } 55 | result.stdout = stdoutBuilder.toString(); 56 | result.stderr = stderrBuilder.toString(); 57 | result.execTime = System.currentTimeMillis() - startTime; 58 | result.exitCode = process.exitValue(); 59 | return result; 60 | } 61 | 62 | public static class ProcessResult { 63 | public int exitCode; 64 | public long execTime; 65 | public boolean timeout; 66 | public String stdout = ""; 67 | public String stderr = ""; 68 | public String commandLine; 69 | 70 | public void check(String processName) { 71 | if (!this.timeout && this.exitCode == 0) { 72 | return; 73 | } 74 | if (this.timeout) { 75 | System.err.println(processName + " 编译超时,可能是您编译的类和方法太多或者机器性能较低导致编译超时"); 76 | } else if (this.commandLine.contains("zig") && this.commandLine.contains("jnic")) { 77 | System.err.println(processName + " 编译错误"); 78 | System.err.println("已为您自动清理zig临时文件:" + SetupManager.getZigGlobalCacheDirectory(true) + " 请重新运行"); 79 | System.out.println("如果再次运行失败,请手动删除后重试,手动删除后仍失败请反馈问题给开发者"); 80 | } 81 | System.err.println("stdout: \n" + this.stdout); 82 | System.err.println("stderr: \n" + this.stderr); 83 | throw new RuntimeException(processName + " " + (this.timeout ? "命令执行超时" : "命令执行出错")); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/FrameHandler.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.instructions; 3 | 4 | import cc.nuym.jnic.utils.MethodContext; 5 | import cc.nuym.jnic.MethodProcessor; 6 | import cc.nuym.jnic.utils.Util; 7 | import org.objectweb.asm.Opcodes; 8 | import org.objectweb.asm.Type; 9 | import org.objectweb.asm.tree.FrameNode; 10 | import org.objectweb.asm.tree.LabelNode; 11 | 12 | import java.util.Arrays; 13 | import java.util.function.Consumer; 14 | 15 | public class FrameHandler 16 | implements InstructionTypeHandler { 17 | @Override 18 | public void accept(MethodContext context, FrameNode node) { 19 | Consumer appendLocal = local -> { 20 | if (local instanceof String) { 21 | context.locals.add(MethodProcessor.TYPE_TO_STACK[Type.OBJECT]); 22 | } else if (local instanceof LabelNode) { 23 | context.locals.add(MethodProcessor.TYPE_TO_STACK[Type.OBJECT]); 24 | } else { 25 | context.locals.add(MethodProcessor.STACK_TO_STACK[(int) local]); 26 | } 27 | }; 28 | 29 | Consumer appendStack = stack -> { 30 | if (stack instanceof String) { 31 | context.stack.add(MethodProcessor.TYPE_TO_STACK[Type.OBJECT]); 32 | } else if (stack instanceof LabelNode) { 33 | context.stack.add(MethodProcessor.TYPE_TO_STACK[Type.OBJECT]); 34 | } else { 35 | context.stack.add(MethodProcessor.STACK_TO_STACK[(int) stack]); 36 | } 37 | }; 38 | 39 | switch (node.type) { 40 | case Opcodes.F_APPEND: 41 | node.local.forEach(appendLocal); 42 | context.stack.clear(); 43 | break; 44 | 45 | case Opcodes.F_CHOP: 46 | node.local.forEach(item -> context.locals.remove(context.locals.size() - 1)); 47 | context.stack.clear(); 48 | break; 49 | 50 | case Opcodes.F_NEW: 51 | case Opcodes.F_FULL: 52 | context.locals.clear(); 53 | context.stack.clear(); 54 | node.local.forEach(appendLocal); 55 | node.stack.forEach(appendStack); 56 | break; 57 | 58 | case Opcodes.F_SAME: 59 | context.stack.clear(); 60 | break; 61 | 62 | case Opcodes.F_SAME1: 63 | context.stack.clear(); 64 | appendStack.accept(node.stack.get(0)); 65 | break; 66 | } 67 | } 68 | 69 | @Override 70 | public String insnToString(MethodContext context, FrameNode node) { 71 | return String.format("FRAME %s L: %s S: %s", Util.getOpcodesString(node.type, "F_"), 72 | node.local == null ? "null" : Arrays.toString(node.local.toArray(new Object[0])), 73 | node.stack == null ? "null" : Arrays.toString(node.stack.toArray(new Object[0]))); 74 | } 75 | 76 | @Override 77 | public int getNewStackPointer(FrameNode node, int currentStackPointer) { 78 | switch (node.type) { 79 | case Opcodes.F_APPEND: 80 | case Opcodes.F_SAME: 81 | case Opcodes.F_CHOP: 82 | return 0; 83 | case Opcodes.F_NEW: 84 | case Opcodes.F_FULL: 85 | return node.stack.stream().mapToInt(argument -> Math.max(1, argument instanceof Integer ? 86 | MethodProcessor.STACK_TO_STACK[(int) argument] : MethodProcessor.TYPE_TO_STACK[Type.OBJECT])).sum(); 87 | case Opcodes.F_SAME1: 88 | return node.stack.stream().limit(1).mapToInt(argument -> Math.max(1, argument instanceof Integer ? 89 | MethodProcessor.STACK_TO_STACK[(int) argument] : MethodProcessor.TYPE_TO_STACK[Type.OBJECT])).sum(); 90 | } 91 | throw new RuntimeException(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/asm/ClassMetadataReader.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.asm; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassVisitor; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.jar.JarFile; 13 | 14 | public class ClassMetadataReader 15 | { 16 | private final List classPath; 17 | 18 | public ClassMetadataReader(final List classPath) { 19 | this.classPath = classPath; 20 | } 21 | 22 | public List getCp() { 23 | return Collections.unmodifiableList(this.classPath); 24 | } 25 | 26 | public ClassMetadataReader() { 27 | this.classPath = new ArrayList<>(); 28 | } 29 | 30 | public void acceptVisitor(final byte[] classData, final ClassVisitor visitor) { 31 | new ClassReader(classData).accept(visitor, 0); 32 | } 33 | 34 | public void acceptVisitor(final String className, final ClassVisitor visitor) throws IOException, ClassNotFoundException { 35 | this.acceptVisitor(this.getClassData(className), visitor); 36 | } 37 | 38 | private static byte[] read(final InputStream input) throws IOException { 39 | try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) { 40 | final byte[] buffer = new byte[4096]; 41 | for (int length = input.read(buffer); length >= 0; length = input.read(buffer)) { 42 | output.write(buffer, 0, length); 43 | } 44 | return output.toByteArray(); 45 | } 46 | } 47 | 48 | public byte[] getClassData(final String className) throws IOException, ClassNotFoundException { 49 | for (final JarFile file : this.classPath) { 50 | if (file.getEntry(className + ".class") == null) { 51 | continue; 52 | } 53 | try (final InputStream in = file.getInputStream(file.getEntry(className + ".class"))) { 54 | return read(in); 55 | } 56 | } 57 | throw new ClassNotFoundException(className); 58 | } 59 | 60 | public String getSuperClass(final String type) { 61 | if (type.equals("java/lang/Object")) { 62 | return null; 63 | } 64 | try { 65 | return this.getSuperClassASM(type); 66 | } 67 | catch (IOException | ClassNotFoundException ex2) { 68 | return "java/lang/Object"; 69 | } 70 | } 71 | 72 | protected String getSuperClassASM(final String type) throws IOException, ClassNotFoundException { 73 | final CheckSuperClassVisitor cv = new CheckSuperClassVisitor(); 74 | this.acceptVisitor(type, cv); 75 | return cv.superClassName; 76 | } 77 | 78 | public ArrayList getSuperClasses(String type) { 79 | final ArrayList superclasses = new ArrayList<>(1); 80 | superclasses.add(type); 81 | while ((type = this.getSuperClass(type)) != null) { 82 | superclasses.add(type); 83 | } 84 | Collections.reverse(superclasses); 85 | return superclasses; 86 | } 87 | 88 | public void close() { 89 | this.classPath.forEach(file -> { 90 | try { 91 | file.close(); 92 | } 93 | catch (IOException ignored) {} 94 | }); 95 | } 96 | 97 | private static class CheckSuperClassVisitor extends ClassVisitor 98 | { 99 | String superClassName; 100 | 101 | public CheckSuperClassVisitor() { 102 | super(458752); 103 | } 104 | 105 | @Override 106 | public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { 107 | this.superClassName = superName; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/Zipper.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 4 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 5 | import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.file.CopyOption; 11 | import java.nio.file.Files; 12 | import java.nio.file.OpenOption; 13 | import java.nio.file.Path; 14 | import java.nio.file.attribute.FileAttribute; 15 | import java.util.zip.ZipEntry; 16 | import java.util.zip.ZipInputStream; 17 | import java.util.zip.ZipOutputStream; 18 | 19 | public class Zipper 20 | { 21 | public static void extract(final Path archive, final Path target) throws IOException { 22 | final String name = archive.toFile().getName(); 23 | if (name.contains("zip")) { 24 | unzip(archive, target); 25 | } 26 | else if (name.contains("tar.xz")) { 27 | unTarXZ(archive, target); 28 | } 29 | else { 30 | if (!name.contains("tar")) { 31 | throw new RuntimeException("unsupported content type "); 32 | } 33 | unTar(archive, target); 34 | } 35 | } 36 | 37 | public static void unzip(final Path zip, final Path target) throws IOException { 38 | try (final ZipInputStream zin = new ZipInputStream(new BufferedInputStream(Files.newInputStream(zip, new OpenOption[0])))) { 39 | ZipEntry entry; 40 | while ((entry = zin.getNextEntry()) != null) { 41 | extractEntry(target, zin, entry.getName(), entry.isDirectory()); 42 | } 43 | } 44 | } 45 | 46 | public static void unXzip(final Path xzip, final Path target) throws IOException { 47 | try (final XZCompressorInputStream xin = new XZCompressorInputStream(new BufferedInputStream(Files.newInputStream(xzip, new OpenOption[0])))) { 48 | Files.copy(xin, target, new CopyOption[0]); 49 | } 50 | } 51 | 52 | public static void unTar(final Path tar, final Path target) throws IOException { 53 | try (final TarArchiveInputStream tin = new TarArchiveInputStream(new BufferedInputStream(Files.newInputStream(tar, new OpenOption[0])))) { 54 | TarArchiveEntry entry; 55 | while ((entry = tin.getNextTarEntry()) != null) { 56 | extractEntry(target, tin, entry.getName(), entry.isDirectory()); 57 | } 58 | } 59 | } 60 | 61 | public static void unTarXZ(final Path tar, final Path target) throws IOException { 62 | try (final XZCompressorInputStream xzcis = new XZCompressorInputStream(new BufferedInputStream(Files.newInputStream(tar, new OpenOption[0]))); 63 | final TarArchiveInputStream tin = new TarArchiveInputStream(xzcis, 1024)) { 64 | TarArchiveEntry entry; 65 | while ((entry = tin.getNextTarEntry()) != null) { 66 | extractEntry(target, tin, entry.getName(), entry.isDirectory()); 67 | } 68 | } 69 | } 70 | 71 | private static void extractEntry(final Path target, final InputStream in, final String entryName, final boolean isDirectory) throws IOException { 72 | final Path entryPath = target.resolve(entryName); 73 | if (isDirectory) { 74 | Files.createDirectories(entryPath, (FileAttribute[])new FileAttribute[0]); 75 | } 76 | else { 77 | final Path dir = entryPath.getParent(); 78 | Files.createDirectories(dir, (FileAttribute[])new FileAttribute[0]); 79 | Files.copy(in, entryPath, new CopyOption[0]); 80 | } 81 | } 82 | public static void writeToFile(ZipOutputStream outputStream, InputStream inputStream) throws Throwable { 83 | byte[] buffer = new byte[4096]; 84 | try { 85 | while (inputStream.available() > 0) { 86 | int data = inputStream.read(buffer); 87 | outputStream.write(buffer, 0, data); 88 | } 89 | } finally { 90 | inputStream.close(); 91 | outputStream.closeEntry(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/cache/CachedClassInfo.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.cache; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | public class CachedClassInfo 8 | { 9 | private final String clazz; 10 | private final String name; 11 | private final int id; 12 | private final String desc; 13 | private final boolean isStatic; 14 | private final List cachedFields; 15 | private final List cachedMethods; 16 | 17 | public CachedClassInfo(final String clazz, final String name, final String desc, final int id, final boolean isStatic) { 18 | this.cachedFields = new ArrayList<>(); 19 | this.cachedMethods = new ArrayList<>(); 20 | this.clazz = clazz; 21 | this.name = name; 22 | this.desc = desc; 23 | this.id = id; 24 | this.isStatic = isStatic; 25 | } 26 | 27 | @Override 28 | public boolean equals(final Object o) { 29 | if (this == o) { 30 | return true; 31 | } 32 | if (o == null || this.getClass() != o.getClass()) { 33 | return false; 34 | } 35 | final CachedClassInfo that = (CachedClassInfo)o; 36 | return this.isStatic == that.isStatic && this.clazz.equals(that.clazz) && this.name.equals(that.name) && this.desc.equals(that.desc); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return Objects.hash(this.clazz, this.name, this.desc, this.isStatic); 42 | } 43 | 44 | public List getCachedFields() { 45 | return this.cachedFields; 46 | } 47 | 48 | public List getCachedMethods() { 49 | return this.cachedMethods; 50 | } 51 | 52 | public void addCachedField(final CachedFieldInfo cachedFieldInfo) { 53 | boolean contains = false; 54 | for (final CachedFieldInfo cachedField : this.cachedFields) { 55 | if (cachedField.equals(cachedFieldInfo)) { 56 | contains = true; 57 | break; 58 | } 59 | } 60 | if (!contains) { 61 | cachedFieldInfo.setId(this.cachedFields.size()); 62 | this.cachedFields.add(cachedFieldInfo); 63 | } 64 | } 65 | 66 | public int getCachedFieldId(final CachedFieldInfo cachedFieldInfo) { 67 | for (final CachedFieldInfo cachedField : this.cachedFields) { 68 | if (cachedField.equals(cachedFieldInfo)) { 69 | return cachedField.getId(); 70 | } 71 | } 72 | cachedFieldInfo.setId(this.cachedFields.size()); 73 | this.cachedFields.add(cachedFieldInfo); 74 | return cachedFieldInfo.getId(); 75 | } 76 | 77 | public int getCachedMethodId(final CachedMethodInfo cachedMethodInfo) { 78 | for (final CachedMethodInfo methodInfo : this.cachedMethods) { 79 | if (methodInfo.equals(cachedMethodInfo)) { 80 | return methodInfo.getId(); 81 | } 82 | } 83 | cachedMethodInfo.setId(this.cachedMethods.size()); 84 | this.cachedMethods.add(cachedMethodInfo); 85 | return cachedMethodInfo.getId(); 86 | } 87 | 88 | public void addCachedMethod(final CachedMethodInfo cachedMethodInfo) { 89 | boolean contains = false; 90 | for (final CachedMethodInfo methodInfo : this.cachedMethods) { 91 | if (methodInfo.equals(cachedMethodInfo)) { 92 | contains = true; 93 | break; 94 | } 95 | } 96 | if (!contains) { 97 | cachedMethodInfo.setId(this.cachedMethods.size()); 98 | this.cachedMethods.add(cachedMethodInfo); 99 | } 100 | } 101 | 102 | public int getId() { 103 | return this.id; 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | return "CachedClassInfo{clazz='" + this.clazz + '\'' + ", name='" + this.name + '\'' + ", id=" + this.id + ", desc='" + this.desc + '\'' + ", isStatic=" + this.isStatic + ", cachedFields=" + this.cachedFields + ", cachedMethods=" + this.cachedMethods + '}'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/Base64Util.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | public class Base64Util 4 | { 5 | private static char[] base64EncodeChars; 6 | private static byte[] base64DecodeChars; 7 | 8 | public static String encrypt(final byte[] data) { 9 | final StringBuffer sb = new StringBuffer(); 10 | final int len = data.length; 11 | int i = 0; 12 | while (i < len) { 13 | final int b1 = data[i++] & 0xFF; 14 | if (i == len) { 15 | sb.append(Base64Util.base64EncodeChars[b1 >>> 2]); 16 | sb.append(Base64Util.base64EncodeChars[(b1 & 0x3) << 4]); 17 | sb.append("=="); 18 | break; 19 | } 20 | final int b2 = data[i++] & 0xFF; 21 | if (i == len) { 22 | sb.append(Base64Util.base64EncodeChars[b1 >>> 2]); 23 | sb.append(Base64Util.base64EncodeChars[(b1 & 0x3) << 4 | (b2 & 0xF0) >>> 4]); 24 | sb.append(Base64Util.base64EncodeChars[(b2 & 0xF) << 2]); 25 | sb.append("="); 26 | break; 27 | } 28 | final int b3 = data[i++] & 0xFF; 29 | sb.append(Base64Util.base64EncodeChars[b1 >>> 2]); 30 | sb.append(Base64Util.base64EncodeChars[(b1 & 0x3) << 4 | (b2 & 0xF0) >>> 4]); 31 | sb.append(Base64Util.base64EncodeChars[(b2 & 0xF) << 2 | (b3 & 0xC0) >>> 6]); 32 | sb.append(Base64Util.base64EncodeChars[b3 & 0x3F]); 33 | } 34 | return sb.toString(); 35 | } 36 | 37 | public static byte[] decrypt(final String str) throws Exception { 38 | final StringBuffer sb = new StringBuffer(); 39 | final byte[] data = str.getBytes("US-ASCII"); 40 | final int len = data.length; 41 | int i = 0; 42 | while (i < len) { 43 | int b1; 44 | do { 45 | b1 = Base64Util.base64DecodeChars[data[i++]]; 46 | } while (i < len && b1 == -1); 47 | if (b1 == -1) { 48 | break; 49 | } 50 | int b2; 51 | do { 52 | b2 = Base64Util.base64DecodeChars[data[i++]]; 53 | } while (i < len && b2 == -1); 54 | if (b2 == -1) { 55 | break; 56 | } 57 | sb.append((char)(b1 << 2 | (b2 & 0x30) >>> 4)); 58 | int b3; 59 | do { 60 | b3 = data[i++]; 61 | if (b3 == 61) { 62 | return sb.toString().getBytes("iso8859-1"); 63 | } 64 | b3 = Base64Util.base64DecodeChars[b3]; 65 | } while (i < len && b3 == -1); 66 | if (b3 == -1) { 67 | break; 68 | } 69 | sb.append((char)((b2 & 0xF) << 4 | (b3 & 0x3C) >>> 2)); 70 | int b4; 71 | do { 72 | b4 = data[i++]; 73 | if (b4 == 61) { 74 | return sb.toString().getBytes("iso8859-1"); 75 | } 76 | b4 = Base64Util.base64DecodeChars[b4]; 77 | } while (i < len && b4 == -1); 78 | if (b4 == -1) { 79 | break; 80 | } 81 | sb.append((char)((b3 & 0x3) << 6 | b4)); 82 | } 83 | return sb.toString().getBytes("iso8859-1"); 84 | } 85 | 86 | static { 87 | Base64Util.base64EncodeChars = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; 88 | Base64Util.base64DecodeChars = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.spec.GCMParameterSpec; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.charset.Charset; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.*; 12 | import java.nio.file.attribute.BasicFileAttributes; 13 | 14 | public class FileUtils 15 | { 16 | private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; 17 | private static final int TAG_LENGTH_BIT = 128; 18 | private static final int IV_LENGTH_BYTE = 12; 19 | private static final int SALT_LENGTH_BYTE = 16; 20 | private static final Charset UTF_8; 21 | 22 | public static byte[] encrypt(final byte[] pText, final String password) throws Exception { 23 | final byte[] salt = CryptoUtils.getRandomNonce(16); 24 | final byte[] iv = CryptoUtils.getRandomNonce(12); 25 | final SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); 26 | final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); 27 | cipher.init(1, aesKeyFromPassword, new GCMParameterSpec(128, iv)); 28 | final byte[] cipherText = cipher.doFinal(pText); 29 | final byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length).put(iv).put(salt).put(cipherText).array(); 30 | return cipherTextWithIvSalt; 31 | } 32 | 33 | private static byte[] decrypt(final byte[] cText, final String password) throws Exception { 34 | final ByteBuffer bb = ByteBuffer.wrap(cText); 35 | final byte[] iv = new byte[12]; 36 | bb.get(iv); 37 | final byte[] salt = new byte[16]; 38 | bb.get(salt); 39 | final byte[] cipherText = new byte[bb.remaining()]; 40 | bb.get(cipherText); 41 | final SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); 42 | final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); 43 | cipher.init(2, aesKeyFromPassword, new GCMParameterSpec(128, iv)); 44 | final byte[] plainText = cipher.doFinal(cipherText); 45 | return plainText; 46 | } 47 | 48 | public static void encryptFile(final Path fromFile, final Path toFile, final String password) throws Exception { 49 | final byte[] fileContent = Files.readAllBytes(fromFile); 50 | final byte[] encryptedText = encrypt(fileContent, password); 51 | Files.write(toFile, encryptedText, new OpenOption[0]); 52 | } 53 | 54 | public static byte[] decryptFile(final String fromEncryptedFile, final String password) throws Exception { 55 | final byte[] fileContent = Files.readAllBytes(Paths.get(fromEncryptedFile, new String[0])); 56 | return decrypt(fileContent, password); 57 | } 58 | 59 | public static void decryptToFile(final Path fromFile, final Path toFile, final String password) throws Exception { 60 | final byte[] fileContent = Files.readAllBytes(fromFile); 61 | Files.write(toFile, decrypt(fileContent, password), new OpenOption[0]); 62 | } 63 | 64 | public static void clearDirectory(final String path) { 65 | final File directory = new File(path); 66 | if (!directory.exists()) { 67 | return; 68 | } 69 | final Path basePath = directory.toPath(); 70 | try { 71 | Files.walkFileTree(basePath, new SimpleFileVisitor() { 72 | @Override 73 | public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 74 | Files.delete(file); 75 | return FileVisitResult.CONTINUE; 76 | } 77 | 78 | @Override 79 | public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 80 | Files.delete(dir); 81 | return FileVisitResult.CONTINUE; 82 | } 83 | }); 84 | } 85 | catch (Exception e) { 86 | System.out.println("删除" + path + "失败,请先手动删除后再运行本程序!"); 87 | } 88 | } 89 | 90 | static { 91 | UTF_8 = StandardCharsets.UTF_8; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/ConsoleColors.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | public class ConsoleColors { 4 | // Reset 5 | public static final String RESET = "\033[0m"; // Text Reset 6 | 7 | // Regular Colors 8 | public static final String BLACK = "\033[0;30m"; // BLACK 9 | public static final String RED = "\033[0;31m"; // RED 10 | public static final String GREEN = "\033[0;32m"; // GREEN 11 | public static final String YELLOW = "\033[0;33m"; // YELLOW 12 | public static final String BLUE = "\033[0;34m"; // BLUE 13 | public static final String PURPLE = "\033[0;35m"; // PURPLE 14 | public static final String CYAN = "\033[0;36m"; // CYAN 15 | public static final String WHITE = "\033[0;37m"; // WHITE 16 | 17 | // Bold 18 | public static final String BLACK_BOLD = "\033[1;30m"; // BLACK 19 | public static final String RED_BOLD = "\033[1;31m"; // RED 20 | public static final String GREEN_BOLD = "\033[1;32m"; // GREEN 21 | public static final String YELLOW_BOLD = "\033[1;33m"; // YELLOW 22 | public static final String BLUE_BOLD = "\033[1;34m"; // BLUE 23 | public static final String PURPLE_BOLD = "\033[1;35m"; // PURPLE 24 | public static final String CYAN_BOLD = "\033[1;36m"; // CYAN 25 | public static final String WHITE_BOLD = "\033[1;37m"; // WHITE 26 | 27 | // Underline 28 | public static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACK 29 | public static final String RED_UNDERLINED = "\033[4;31m"; // RED 30 | public static final String GREEN_UNDERLINED = "\033[4;32m"; // GREEN 31 | public static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOW 32 | public static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUE 33 | public static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLE 34 | public static final String CYAN_UNDERLINED = "\033[4;36m"; // CYAN 35 | public static final String WHITE_UNDERLINED = "\033[4;37m"; // WHITE 36 | 37 | // Background 38 | public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK 39 | public static final String RED_BACKGROUND = "\033[41m"; // RED 40 | public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN 41 | public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW 42 | public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE 43 | public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE 44 | public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN 45 | public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE 46 | 47 | // High Intensity 48 | public static final String BLACK_BRIGHT = "\033[0;90m"; // BLACK 49 | public static final String RED_BRIGHT = "\033[0;91m"; // RED 50 | public static final String GREEN_BRIGHT = "\033[0;92m"; // GREEN 51 | public static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOW 52 | public static final String BLUE_BRIGHT = "\033[0;94m"; // BLUE 53 | public static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLE 54 | public static final String CYAN_BRIGHT = "\033[0;96m"; // CYAN 55 | public static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE 56 | 57 | // Bold High Intensity 58 | public static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACK 59 | public static final String RED_BOLD_BRIGHT = "\033[1;91m"; // RED 60 | public static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREEN 61 | public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOW 62 | public static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUE 63 | public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLE 64 | public static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYAN 65 | public static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE 66 | 67 | // High Intensity backgrounds 68 | public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK 69 | public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED 70 | public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN 71 | public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW 72 | public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE 73 | public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE 74 | public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN 75 | public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/MultiANewArrayHandler.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.instructions; 3 | 4 | import cc.nuym.jnic.utils.MethodContext; 5 | import org.objectweb.asm.Type; 6 | import org.objectweb.asm.tree.MultiANewArrayInsnNode; 7 | 8 | import java.util.stream.Collectors; 9 | import java.util.stream.IntStream; 10 | 11 | public class MultiANewArrayHandler 12 | extends GenericInstructionHandler { 13 | @Override 14 | protected void process(MethodContext context, MultiANewArrayInsnNode node) { 15 | this.instructionName = null; 16 | context.output.append(this.genCode(context, node)); 17 | } 18 | 19 | @Override 20 | public String insnToString(MethodContext context, MultiANewArrayInsnNode node) { 21 | return String.format("MULTIANEWARRAY %d %s", node.dims, node.desc); 22 | } 23 | 24 | @Override 25 | public int getNewStackPointer(MultiANewArrayInsnNode node, int currentStackPointer) { 26 | return currentStackPointer - node.dims + 1; 27 | } 28 | 29 | private String genCode(MethodContext context, MultiANewArrayInsnNode node) { 30 | String code = "{\n"; 31 | int dimensions = node.dims; 32 | code = code + "jsize dim[] = " + String.format("{ %s };\n", IntStream.range(0, dimensions).mapToObj(i -> String.format("cstack%d.i", i)).collect(Collectors.joining(", "))); 33 | String desc = node.desc; 34 | desc = desc.substring(1); 35 | code = code + "cstack0.l = (*env)->NewObjectArray(env, dim[0], c_" + context.getCachedClasses().getId(desc) + "_(env)->clazz, NULL);\n"; 36 | code = code + this.getSub(context, node, 0, dimensions); 37 | code = code + "};"; 38 | return code; 39 | } 40 | 41 | private String getSub(MethodContext context, MultiANewArrayInsnNode node, int index, int max) { 42 | String code = ""; 43 | if (index < max - 1) { 44 | String space = ""; 45 | for (int i = 0; i < index + 1; ++i) { 46 | space = space + " "; 47 | } 48 | int next = index + 1; 49 | code = code + space + "for(jsize d" + index + " = 0; d" + index + " < dim[" + index + "]; d" + index + "++) {\n"; 50 | String desc = node.desc; 51 | for (int i = 0; i < next + 1; ++i) { 52 | desc = desc.substring(1); 53 | } 54 | String s = space; 55 | switch (Type.getType(desc).getSort()) { 56 | case 1: { 57 | code = code + s + " cstack" + next + ".l = (*env)->NewBooleanArray(env, dim[" + next + "]);\n"; 58 | break; 59 | } 60 | case 2: { 61 | code = code + s + " cstack" + next + ".l = (*env)->NewCharArray(env, dim[" + next + "]);\n"; 62 | break; 63 | } 64 | case 3: { 65 | code = code + s + " cstack" + next + ".l = (*env)->NewByteArray(env, dim[" + next + "]);\n"; 66 | break; 67 | } 68 | case 4: { 69 | code = code + s + " cstack" + next + ".l = (*env)->NewShortArray(env, dim[" + next + "]);\n"; 70 | break; 71 | } 72 | case 5: { 73 | code = code + s + " cstack" + next + ".l = (*env)->NewIntArray(env, dim[" + next + "]);\n"; 74 | break; 75 | } 76 | case 6: { 77 | code = code + s + " cstack" + next + ".l = (*env)->NewFloatArray(env, dim[" + next + "]);\n"; 78 | break; 79 | } 80 | case 7: { 81 | code = code + s + " cstack" + next + ".l = (*env)->NewLongArray(env, dim[" + next + "]);\n"; 82 | break; 83 | } 84 | case 8: { 85 | code = code + s + " cstack" + next + ".l = (*env)->NewDoubleArray(env, dim[" + next + "]);\n"; 86 | break; 87 | } 88 | default: { 89 | code = code + s + " cstack" + next + ".l = (*env)->NewObjectArray(env, dim[" + next + "], c_" + context.getCachedClasses().getId(desc) + "_(env)->clazz, NULL);\n"; 90 | } 91 | } 92 | code = code + space + " (*env)->SetObjectArrayElement(env, cstack" + index + ".l, d" + index + ", cstack" + next + ".l);\n"; 93 | code = code + this.getSub(context, node, next, max); 94 | code = code + space + "}\n"; 95 | } 96 | return code; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/InsnHandler.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.instructions; 2 | 3 | import cc.nuym.jnic.utils.MethodContext; 4 | import cc.nuym.jnic.utils.Util; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.InsnNode; 7 | 8 | public class InsnHandler extends GenericInstructionHandler { 9 | 10 | @Override 11 | protected void process(MethodContext context, InsnNode node) { 12 | //context.output.append("//InsnHandler:"+node.getOpcode()+"\n"); 13 | } 14 | 15 | @Override 16 | public String insnToString(MethodContext context, InsnNode node) { 17 | return Util.getOpcodeString(node.getOpcode()); 18 | } 19 | 20 | @Override 21 | public int getNewStackPointer(InsnNode node, int currentStackPointer) { 22 | switch (node.getOpcode()) { 23 | case Opcodes.NOP: 24 | case Opcodes.ARRAYLENGTH: 25 | case Opcodes.RETURN: 26 | case Opcodes.I2S: 27 | case Opcodes.I2C: 28 | case Opcodes.I2B: 29 | case Opcodes.D2L: 30 | case Opcodes.F2I: 31 | case Opcodes.L2D: 32 | case Opcodes.I2F: 33 | case Opcodes.DNEG: 34 | case Opcodes.FNEG: 35 | case Opcodes.LNEG: 36 | case Opcodes.INEG: 37 | case Opcodes.SWAP: 38 | case Opcodes.DALOAD: 39 | case Opcodes.LALOAD: 40 | return currentStackPointer; 41 | case Opcodes.ACONST_NULL: 42 | case Opcodes.F2D: 43 | case Opcodes.F2L: 44 | case Opcodes.I2D: 45 | case Opcodes.I2L: 46 | case Opcodes.DUP_X2: 47 | case Opcodes.DUP_X1: 48 | case Opcodes.DUP: 49 | case Opcodes.FCONST_2: 50 | case Opcodes.FCONST_1: 51 | case Opcodes.FCONST_0: 52 | case Opcodes.ICONST_5: 53 | case Opcodes.ICONST_4: 54 | case Opcodes.ICONST_3: 55 | case Opcodes.ICONST_2: 56 | case Opcodes.ICONST_1: 57 | case Opcodes.ICONST_0: 58 | case Opcodes.ICONST_M1: 59 | return currentStackPointer + 1; 60 | case Opcodes.LCONST_0: 61 | case Opcodes.DUP2_X2: 62 | case Opcodes.DUP2_X1: 63 | case Opcodes.DUP2: 64 | case Opcodes.DCONST_1: 65 | case Opcodes.DCONST_0: 66 | case Opcodes.LCONST_1: 67 | return currentStackPointer + 2; 68 | case Opcodes.IALOAD: 69 | case Opcodes.MONITOREXIT: 70 | case Opcodes.MONITORENTER: 71 | case Opcodes.ARETURN: 72 | case Opcodes.FRETURN: 73 | case Opcodes.IRETURN: 74 | case Opcodes.FCMPG: 75 | case Opcodes.FCMPL: 76 | case Opcodes.D2F: 77 | case Opcodes.D2I: 78 | case Opcodes.L2F: 79 | case Opcodes.L2I: 80 | case Opcodes.IXOR: 81 | case Opcodes.IOR: 82 | case Opcodes.IAND: 83 | case Opcodes.LUSHR: 84 | case Opcodes.IUSHR: 85 | case Opcodes.LSHR: 86 | case Opcodes.ISHR: 87 | case Opcodes.LSHL: 88 | case Opcodes.ISHL: 89 | case Opcodes.FREM: 90 | case Opcodes.IREM: 91 | case Opcodes.FDIV: 92 | case Opcodes.IDIV: 93 | case Opcodes.FMUL: 94 | case Opcodes.IMUL: 95 | case Opcodes.FSUB: 96 | case Opcodes.ISUB: 97 | case Opcodes.FADD: 98 | case Opcodes.IADD: 99 | case Opcodes.POP: 100 | case Opcodes.SALOAD: 101 | case Opcodes.CALOAD: 102 | case Opcodes.BALOAD: 103 | case Opcodes.AALOAD: 104 | case Opcodes.FALOAD: 105 | case Opcodes.ATHROW: 106 | return currentStackPointer - 1; 107 | case Opcodes.IASTORE: 108 | case Opcodes.DCMPG: 109 | case Opcodes.DCMPL: 110 | case Opcodes.LCMP: 111 | case Opcodes.SASTORE: 112 | case Opcodes.CASTORE: 113 | case Opcodes.BASTORE: 114 | case Opcodes.AASTORE: 115 | case Opcodes.FASTORE: 116 | return currentStackPointer - 3; 117 | case Opcodes.LASTORE: 118 | case Opcodes.DASTORE: 119 | return currentStackPointer - 4; 120 | case Opcodes.POP2: 121 | case Opcodes.DRETURN: 122 | case Opcodes.LRETURN: 123 | case Opcodes.LXOR: 124 | case Opcodes.LOR: 125 | case Opcodes.LAND: 126 | case Opcodes.DREM: 127 | case Opcodes.LREM: 128 | case Opcodes.DDIV: 129 | case Opcodes.LDIV: 130 | case Opcodes.DMUL: 131 | case Opcodes.LMUL: 132 | case Opcodes.DSUB: 133 | case Opcodes.LSUB: 134 | case Opcodes.DADD: 135 | case Opcodes.LADD: 136 | return currentStackPointer - 2; 137 | } 138 | throw new RuntimeException(String.valueOf(node.getOpcode())); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /loader/src/main/java/JNICLoader.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.FileOutputStream; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | public class JNICLoader { 10 | public static void registerNativesForClass(int index, Class clazz) { 11 | } 12 | 13 | 14 | static { 15 | String osname = System.getProperty("os.name").toLowerCase(); 16 | String osarch = System.getProperty("os.arch").toLowerCase(); 17 | String arch = "raw"+osarch; 18 | String name = "raw"+osname; 19 | switch (osarch) { 20 | case "x86_64": 21 | case "amd64": { 22 | arch = "x64"; 23 | break; 24 | } 25 | case "aarch64": { 26 | arch = "arm64"; 27 | break; 28 | } 29 | case "arm": { 30 | arch = "arm32"; 31 | break; 32 | } 33 | case "x86": { 34 | arch = "x86"; 35 | break; 36 | } 37 | } 38 | if (osname.contains("nix") || osname.contains("nux") || osname.contains("aix")) { 39 | name = "linux.so"; 40 | } else if (osname.contains("win")) { 41 | name = "windows.dll"; 42 | } else if (osname.contains("mac")) { 43 | name = "macos.dylib"; 44 | } 45 | String data = String.format("/dev/jnic/lib/40db034e-902c-4d1b-a58d-b847a6cc845a.dat", JNICLoader.class.getPackage().getName().replace(".", "/")); 46 | 47 | String osName = System.getProperty("os.name"); 48 | if(osName.contains("Linux")&&!arch.contains("arm64")) { 49 | String path = "/usr/lib/libc.so"; 50 | boolean isTextFile = false; 51 | try { 52 | Path path1 = Paths.get(path); 53 | isTextFile = Files.isRegularFile(path1) && 54 | Files.isReadable(path1) && 55 | Files.size(path1) > 0; 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | if(isTextFile) { 60 | ElFFix(); 61 | } 62 | } 63 | 64 | File lib; 65 | File dat; 66 | try { 67 | File temp = new File(System.getProperty("java.io.tmpdir")); 68 | if (!temp.exists()) { 69 | temp.mkdirs(); 70 | } 71 | lib = File.createTempFile("lib", null); 72 | dat = File.createTempFile("dat", null); 73 | lib.deleteOnExit(); 74 | dat.deleteOnExit(); 75 | if (!lib.exists()) 76 | throw new IOException(); 77 | if (!dat.exists()) 78 | throw new IOException(); 79 | } 80 | catch (IOException a7) { 81 | throw new UnsatisfiedLinkError("Failed to create temp file"); 82 | } 83 | try { 84 | InputStream inputStream = JNICLoader.class.getResourceAsStream(data); 85 | if (inputStream == null) { 86 | throw new UnsatisfiedLinkError(String.format("Failed to open dat file: %s", data)); 87 | } 88 | try (FileOutputStream fileOutputStream = new FileOutputStream(dat)) { 89 | byte[] buffer = new byte[8192]; // Read in buffer to increase speed at cost of memory consumption (< 1 MiB) 90 | int bytesRead; 91 | while ((bytesRead = inputStream.read(buffer)) != -1) { 92 | fileOutputStream.write(buffer, 0, bytesRead); 93 | } 94 | inputStream.close(); 95 | } 96 | } 97 | catch (IOException exception) { 98 | throw new UnsatisfiedLinkError(String.format("Failed to copy file: %s", exception.getMessage())); 99 | } 100 | 101 | try { 102 | DataTool.extract(dat.getAbsolutePath(),System.getProperty("java.io.tmpdir"),arch+"-"+name,lib.getName()); 103 | } 104 | catch (Exception e) { 105 | System.out.println(new StringBuilder().insert(0, "Failed load library:").append(lib.getAbsolutePath()).toString()); 106 | throw new RuntimeException(e); 107 | } 108 | try { 109 | System.load(lib.getAbsolutePath()); 110 | } 111 | catch (UnsatisfiedLinkError e) { 112 | System.out.println(new StringBuilder().insert(0, "Failed load library:").append(lib.getAbsolutePath()).toString()); 113 | e.printStackTrace(); 114 | } 115 | } 116 | 117 | private static void ElFFix() { 118 | if (!isLink()) { 119 | String osName = System.getProperty("os.name"); 120 | try { 121 | if (osName.contains("Linux")) { 122 | // 备份 123 | Process process1 = Runtime.getRuntime().exec("cp /usr/lib/libc.so /usr/lib/libc.so.bak"); 124 | process1.waitFor(); 125 | 126 | // 删除 127 | Process process2 = Runtime.getRuntime().exec("rm /usr/lib/libc.so"); 128 | process2.waitFor(); 129 | 130 | // 创建符号链接 131 | Process process3 = Runtime.getRuntime().exec("ln -s /usr/libc.so.6 /usr/lib/libc.so"); 132 | process3.waitFor(); 133 | //System.out.println("ElFFix success!"); 134 | } 135 | }catch (IOException e){ 136 | e.printStackTrace(); 137 | }catch (InterruptedException e){ 138 | e.printStackTrace(); 139 | } 140 | } 141 | } 142 | 143 | private static boolean isLink() { 144 | String osName = System.getProperty("os.name"); 145 | if(!osName.contains("Linux")) { 146 | return false; 147 | } 148 | 149 | Path libPath = Paths.get("/usr/lib/libc.so"); 150 | Path lib64Path = Paths.get("/usr/lib64/libc.so"); 151 | 152 | try { 153 | Path symlinkTarget = Files.readSymbolicLink(libPath); 154 | Path symlink64Target = Files.readSymbolicLink(lib64Path); 155 | if (symlinkTarget.toString().equals("/usr/libc.so.6")) { 156 | return true; 157 | } else { 158 | if (symlink64Target.toString().equals("/usr/libc.so.6")) { 159 | return true; 160 | }else{return false;} 161 | } 162 | } catch (Exception e) { 163 | e.printStackTrace(); 164 | return false; 165 | } 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/ClassMethodFilter.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import cc.nuym.jnic.annotations.Native; 4 | import cc.nuym.jnic.annotations.NotNative; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.objectweb.asm.tree.MethodNode; 9 | 10 | import java.util.List; 11 | 12 | public class ClassMethodFilter { 13 | private static final String NATIVE_ANNOTATION_DESC = Type.getDescriptor(Native.class); 14 | private static final String NOT_NATIVE_ANNOTATION_DESC = Type.getDescriptor(NotNative.class); 15 | 16 | private final List blackList; 17 | private final List whiteList; 18 | private final boolean useAnnotations; 19 | 20 | private static final AntPathMatcher pathMatcher = new AntPathMatcher(); 21 | 22 | public ClassMethodFilter(List blackList, List whiteList, boolean useAnnotations) { 23 | this.blackList = blackList; 24 | this.whiteList = whiteList; 25 | this.useAnnotations = useAnnotations; 26 | } 27 | 28 | public boolean shouldProcess(ClassNode classNode) { 29 | if ((classNode.access & Opcodes.ACC_INTERFACE) != 0) { 30 | return false; 31 | } 32 | if (Util.isValidJavaFullClassName(classNode.name.replaceAll("/", "."))) { 33 | return false; 34 | } 35 | if (this.blackList != null) { 36 | for (String black : this.blackList) { 37 | if (!black.contains("#")) { 38 | if (pathMatcher.matchStart(black, classNode.name)) { 39 | return false; 40 | } 41 | } 42 | } 43 | } 44 | 45 | boolean toMethod = false; 46 | 47 | if (this.whiteList != null && this.whiteList.size() > 0) { 48 | for (String white : this.whiteList) { 49 | if (!white.contains("#")) { 50 | if (pathMatcher.matchStart(white, classNode.name)) { 51 | return true; 52 | } 53 | } else { 54 | String whiteClass = white.split("#")[0]; 55 | if (pathMatcher.matchStart(whiteClass, classNode.name)) { 56 | toMethod = true; 57 | } 58 | } 59 | } 60 | if (!toMethod) { 61 | return false; 62 | } 63 | } 64 | 65 | if (useAnnotations) { 66 | if (classNode.invisibleAnnotations != null && 67 | classNode.invisibleAnnotations.stream().anyMatch(annotationNode -> 68 | annotationNode.desc.equals(NATIVE_ANNOTATION_DESC))) { 69 | return true; 70 | } 71 | } 72 | return classNode.methods.stream().anyMatch(methodNode -> this.shouldProcess(classNode, methodNode)); 73 | } 74 | 75 | public boolean shouldProcess(ClassNode classNode, MethodNode methodNode) { 76 | if (this.blackList != null) { 77 | for (String black : this.blackList) { 78 | if (black.contains("#")) { 79 | String blackClass = black.split("#")[0]; 80 | if (pathMatcher.matchStart(blackClass, classNode.name)) { 81 | String blackMethod = black.split("#")[1]; 82 | if (blackMethod.contains("!")) { 83 | if (pathMatcher.matchStart(blackMethod, methodNode.name + '!' + methodNode.desc)) { 84 | return false; 85 | } 86 | } else { 87 | if (pathMatcher.matchStart(blackMethod, methodNode.name)) { 88 | return false; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | if (this.whiteList != null && this.whiteList.size() > 0) { 96 | boolean bl = false; 97 | for (String white : this.whiteList) { 98 | if (white.contains("#")) { 99 | bl = true; 100 | String whiteClass = white.split("#")[0]; 101 | if (pathMatcher.matchStart(whiteClass, classNode.name)) { 102 | /* if ("".equals(methodNode.name) || "".equals(methodNode.name)) { 103 | return true; 104 | }*/ 105 | String whiteMethod = white.split("#")[1]; 106 | if (whiteMethod.contains("!")) { 107 | if (pathMatcher.matchStart(whiteMethod, methodNode.name + '!' + methodNode.desc)) { 108 | return true; 109 | } 110 | } else { 111 | if (pathMatcher.matchStart(whiteMethod, methodNode.name)) { 112 | return true; 113 | } 114 | } 115 | } 116 | } 117 | } 118 | if (bl) { 119 | return false; 120 | } 121 | } 122 | 123 | if (useAnnotations) { 124 | boolean classIsMarked = classNode.invisibleAnnotations != null && 125 | classNode.invisibleAnnotations.stream().anyMatch(annotationNode -> 126 | annotationNode.desc.equals(NATIVE_ANNOTATION_DESC)); 127 | if (methodNode.invisibleAnnotations != null && 128 | methodNode.invisibleAnnotations.stream().anyMatch(annotationNode -> 129 | annotationNode.desc.equals(NATIVE_ANNOTATION_DESC))) { 130 | return true; 131 | } 132 | return classIsMarked && (methodNode.invisibleAnnotations == null || methodNode.invisibleAnnotations 133 | .stream().noneMatch(annotationNode -> annotationNode.desc.equals( 134 | NOT_NATIVE_ANNOTATION_DESC))); 135 | } else { 136 | return true; 137 | } 138 | 139 | } 140 | 141 | public static void cleanAnnotations(ClassNode classNode) { 142 | if (classNode.invisibleAnnotations != null) { 143 | classNode.invisibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(NATIVE_ANNOTATION_DESC)); 144 | } 145 | classNode.methods.stream() 146 | .filter(methodNode -> methodNode.invisibleAnnotations != null) 147 | .forEach(methodNode -> methodNode.invisibleAnnotations.removeIf(annotationNode -> 148 | annotationNode.desc.equals(NATIVE_ANNOTATION_DESC) || annotationNode.desc.equals(NOT_NATIVE_ANNOTATION_DESC))); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/env/SetupManager.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.env; 2 | 3 | import cc.nuym.jnic.helpers.ProcessHelper; 4 | import cc.nuym.jnic.utils.FileUtils; 5 | import cc.nuym.jnic.utils.Zipper; 6 | import com.google.gson.Gson; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.net.URL; 12 | import java.nio.file.Files; 13 | import java.nio.file.LinkOption; 14 | import java.nio.file.Paths; 15 | import java.nio.file.StandardCopyOption; 16 | import java.util.Arrays; 17 | import java.util.Map; 18 | 19 | public class SetupManager 20 | { 21 | private static String OS; 22 | 23 | public static void init() { 24 | final String platformTypeName = getPlatformTypeName(); 25 | String fileName = null; 26 | String dirName = null; 27 | if (platformTypeName != null && !"".equals(platformTypeName)) { 28 | if (isLinux()) { 29 | fileName = "zig-linux-" + platformTypeName + "-0.9.1.tar.xz"; 30 | dirName = "zig-linux-" + platformTypeName + "-0.9.1"; 31 | } 32 | else if (isMacOS()) { 33 | fileName = "zig-macos-" + platformTypeName + "-0.9.1.tar.xz"; 34 | dirName = "zig-macos-" + platformTypeName + "-0.9.1"; 35 | } 36 | else if (isWindows()) { 37 | fileName = "zig-windows-" + platformTypeName + "-0.9.1.zip"; 38 | dirName = "zig-windows-" + platformTypeName + "-0.9.1"; 39 | } 40 | downloadZigCompiler(fileName, dirName); 41 | return; 42 | } 43 | System.out.println("This system is not supported. Please contact the developer"); 44 | } 45 | 46 | private static String getPlatformTypeName() { 47 | final String lowerCase; 48 | final String platform = lowerCase = System.getProperty("os.arch").toLowerCase(); 49 | String platformTypeName = null; 50 | switch (lowerCase) { 51 | case "x86_64": 52 | case "amd64": { 53 | platformTypeName = "x86_64"; 54 | break; 55 | } 56 | case "aarch64": { 57 | platformTypeName = "aarch64"; 58 | break; 59 | } 60 | case "x86": { 61 | platformTypeName = "i386"; 62 | break; 63 | } 64 | default: { 65 | platformTypeName = ""; 66 | break; 67 | } 68 | } 69 | return platformTypeName; 70 | } 71 | 72 | public static boolean isLinux() { 73 | return SetupManager.OS.contains("linux"); 74 | } 75 | 76 | public static boolean isMacOS() { 77 | return SetupManager.OS.contains("mac") && SetupManager.OS.indexOf("os") > 0; 78 | } 79 | 80 | public static boolean isWindows() { 81 | return SetupManager.OS.contains("windows"); 82 | } 83 | 84 | public static void downloadZigCompiler(final String fileName, final String dirName) { 85 | if (Files.exists(Paths.get(dirName))) { 86 | System.out.println("Found comiler: " + dirName); 87 | return; 88 | } 89 | 90 | try { 91 | final String currentDir = System.getProperty("user.dir"); 92 | System.out.println("Downloading cross compilation tool"); 93 | System.out.println("Download link:https://ziglang.org/download/0.9.1/" + fileName); 94 | final InputStream in = new URL("https://ziglang.org/download/0.9.1/" + fileName).openStream(); 95 | Files.copy(in, Paths.get(currentDir + File.separator + fileName), StandardCopyOption.REPLACE_EXISTING); 96 | System.out.println("Download completed, decompressing"); 97 | unzipFile(currentDir, fileName, currentDir); 98 | deleteFile(currentDir, fileName + ".temp"); 99 | deleteFile(currentDir, fileName); 100 | System.out.println("Installation of cross compilation tool completed"); 101 | if (!isWindows()) { 102 | final String compilePath2 = currentDir + File.separator + dirName + File.separator + "zig"; 103 | ProcessHelper.run(Paths.get(currentDir), 160000L, Arrays.asList("chmod", "777", compilePath2)); 104 | System.out.println("Successfully set running permission"); 105 | } 106 | } 107 | catch (Exception e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | 112 | public static void deleteFile(final String path, final String file) { 113 | new File(path + File.separator + file).delete(); 114 | } 115 | 116 | public static void unzipFile(final String path, final String file, final String destination) { 117 | try { 118 | Zipper.extract(Paths.get(path + File.separator + file), Paths.get(destination)); 119 | } 120 | catch (IOException e) { 121 | throw new RuntimeException(e); 122 | } 123 | } 124 | 125 | public static String getZigGlobalCacheDirectory(final boolean clear) { 126 | final String platformTypeName = getPlatformTypeName(); 127 | String dirName = null; 128 | if (platformTypeName != null && !"".equals(platformTypeName)) { 129 | if (isLinux()) { 130 | dirName = "zig-linux-" + platformTypeName + "-0.9.1"; 131 | } 132 | else if (isMacOS()) { 133 | dirName = "zig-macos-" + platformTypeName + "-0.9.1"; 134 | } 135 | else if (isWindows()) { 136 | dirName = "zig-windows-" + platformTypeName + "-0.9.1"; 137 | } 138 | } 139 | final String currentDir = System.getProperty("user.dir"); 140 | if (Files.exists(Paths.get(currentDir + File.separator + dirName))) { 141 | final String compilePath = currentDir + File.separator + dirName + File.separator + "zig" + (isWindows() ? ".exe" : ""); 142 | if (Files.exists(Paths.get(compilePath))) { 143 | try { 144 | final ProcessHelper.ProcessResult compileRunresult = ProcessHelper.run(Paths.get(currentDir + File.separator + dirName, new String[0]), 160000L, Arrays.asList(compilePath, "env")); 145 | Gson gson = new Gson(); 146 | Map map = gson.fromJson(compileRunresult.stdout, Map.class); 147 | if (clear) { 148 | FileUtils.clearDirectory(map.get("global_cache_dir")); 149 | } 150 | return map.get("global_cache_dir"); 151 | }catch (Throwable e){ 152 | e.printStackTrace(); 153 | } 154 | } 155 | } 156 | System.out.println("Failed to get zig temporary file directory"); 157 | return ""; 158 | } 159 | 160 | static { 161 | SetupManager.OS = System.getProperty("os.name").toLowerCase(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/GenericInstructionHandler.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.instructions; 3 | 4 | import cc.nuym.jnic.utils.CatchesBlock; 5 | import cc.nuym.jnic.utils.MethodContext; 6 | import cc.nuym.jnic.MethodProcessor; 7 | import cc.nuym.jnic.utils.Util; 8 | import org.objectweb.asm.tree.AbstractInsnNode; 9 | import org.objectweb.asm.tree.TryCatchBlockNode; 10 | 11 | import java.util.*; 12 | import java.util.stream.Collectors; 13 | 14 | public abstract class GenericInstructionHandler 15 | implements InstructionTypeHandler { 16 | protected Map props; 17 | protected String instructionName; 18 | protected String trimmedTryCatchBlock; 19 | 20 | @Override 21 | public void accept(MethodContext context, T node) { 22 | props = new HashMap<>(); 23 | List tryCatchBlockNodeList = new ArrayList<>(); 24 | for (TryCatchBlockNode tryCatchBlock : context.method.tryCatchBlocks) { 25 | if (!context.tryCatches.contains(tryCatchBlock)) { 26 | continue; 27 | } 28 | if (tryCatchBlockNodeList.stream().noneMatch(tryCatchBlockNode -> 29 | Objects.equals(tryCatchBlockNode.type, tryCatchBlock.type))) { 30 | tryCatchBlockNodeList.add(tryCatchBlock); 31 | } 32 | } 33 | instructionName = MethodProcessor.INSTRUCTIONS.getOrDefault(node.getOpcode(), "NOTFOUND"); 34 | props.put("line", String.valueOf(context.line)); 35 | StringBuilder tryCatch = new StringBuilder("\n"); 36 | if (tryCatchBlockNodeList.size() > 0) { 37 | String tryCatchLabelName = context.catches.computeIfAbsent(new CatchesBlock(tryCatchBlockNodeList.stream().map(item -> new CatchesBlock.CatchBlock(item.type, item.handler)).collect(Collectors.toList())), key -> String.format("L_CATCH_%d", context.catches.size())); 38 | tryCatch.append("if ((*env)->ExceptionCheck(env)) { \n"); 39 | tryCatch.append(" cstack0.l = (*env)->ExceptionOccurred(env);\n"); 40 | for (TryCatchBlockNode tryCatchBlockNode2 : tryCatchBlockNodeList) { 41 | if (tryCatchBlockNode2.type == null) continue; 42 | tryCatch.append(" if ((*env)->IsInstanceOf(env, cstack0.l, c_").append(context.obfuscator.getCachedClasses().getId(tryCatchBlockNode2.type)).append("_(env)->clazz)) {\n"); 43 | tryCatch.append(" (*env)->ExceptionClear(env);\n"); 44 | tryCatch.append(" goto ").append(tryCatchLabelName).append(";\n }\n"); 45 | } 46 | tryCatch.append(" (*env)->ExceptionClear(env);\n"); 47 | tryCatch.append(" goto ").append(tryCatchLabelName).append(";\n"); 48 | tryCatch.append("}\n"); 49 | } else if ("void".equals(MethodProcessor.CPP_TYPES[context.ret.getSort()])) { 50 | tryCatch.append(context.getSnippets().getSnippet("TRYCATCH_VOID", Util.createMap())); 51 | } else { 52 | String type = ""; 53 | switch (context.ret.getSort()) { 54 | case 9: 55 | case 10: { 56 | type = "l"; 57 | break; 58 | } 59 | case 1: { 60 | type = "z"; 61 | break; 62 | } 63 | case 3: { 64 | type = "b"; 65 | break; 66 | } 67 | case 2: { 68 | type = "c"; 69 | break; 70 | } 71 | case 8: { 72 | type = "d"; 73 | break; 74 | } 75 | case 6: { 76 | type = "f"; 77 | break; 78 | } 79 | case 5: { 80 | type = "i"; 81 | break; 82 | } 83 | case 7: { 84 | type = "j"; 85 | break; 86 | } 87 | case 4: { 88 | type = "s"; 89 | break; 90 | } 91 | default: { 92 | type = "l"; 93 | } 94 | } 95 | tryCatch.append(context.getSnippets().getSnippet("TRYCATCH_EMPTY", Util.createMap("rettype", type))); 96 | } 97 | this.props.put("trycatchhandler", tryCatch.toString()); 98 | this.props.put("rettype", MethodProcessor.CPP_TYPES[context.ret.getSort()]); 99 | switch (context.ret.getSort()) { 100 | case 0: { 101 | this.props.put("retvalue", " return ;"); 102 | break; 103 | } 104 | case 1: { 105 | this.props.put("retvalue", " return temp0.z;"); 106 | break; 107 | } 108 | case 2: { 109 | this.props.put("retvalue", " return temp0.c;"); 110 | break; 111 | } 112 | case 3: { 113 | this.props.put("retvalue", " return temp0.b;"); 114 | break; 115 | } 116 | case 4: { 117 | this.props.put("retvalue", " return temp0.s;"); 118 | break; 119 | } 120 | case 5: { 121 | this.props.put("retvalue", " return temp0.i;"); 122 | break; 123 | } 124 | case 6: { 125 | this.props.put("retvalue", " return temp0.f;"); 126 | break; 127 | } 128 | case 7: { 129 | this.props.put("retvalue", " return temp0.j;"); 130 | break; 131 | } 132 | case 8: { 133 | this.props.put("retvalue", " return temp0.d;"); 134 | break; 135 | } 136 | case 9: { 137 | this.props.put("retvalue", " return (jarray)0;"); 138 | break; 139 | } 140 | case 10: 141 | case 11: { 142 | this.props.put("retvalue", " return temp0.l;"); 143 | break; 144 | } 145 | default: { 146 | this.props.put("retvalue", " return temp0.l;"); 147 | } 148 | } 149 | this.trimmedTryCatchBlock = tryCatch.toString().trim().replace('\n', ' '); 150 | for (int i = -5; i <= 5; ++i) { 151 | this.props.put("stackindex" + (i >= 0 ? Integer.valueOf(i) : "m" + -i), String.valueOf(context.stackPointer + i)); 152 | } 153 | this.process(context, node); 154 | if (this.instructionName != null) { 155 | if ("ATHROW".equals(this.instructionName)) { 156 | this.props.put("class_ptr", "c_" + context.getCachedClasses().getId("java/lang/NullPointerException") + "_"); 157 | } 158 | if ("NEW".equals(this.instructionName) && tryCatchBlockNodeList.size() > 0) { 159 | this.instructionName = "NEW_CATCH"; 160 | } 161 | context.output.append(context.obfuscator.getSnippets().getSnippet(this.instructionName, this.props)); 162 | } 163 | context.output.append("\n"); 164 | } 165 | 166 | protected abstract void process(MethodContext var1, T var2); 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/Main.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic; 2 | 3 | import cc.nuym.jnic.env.SetupManager; 4 | import cc.nuym.jnic.utils.DecryptorClass; 5 | import cc.nuym.jnic.utils.TamperUtils; 6 | import cc.nuym.jnic.xml.Config; 7 | import org.apache.commons.compress.utils.IOUtils; 8 | import org.objectweb.asm.ClassReader; 9 | import org.objectweb.asm.ClassWriter; 10 | import org.objectweb.asm.Opcodes; 11 | import org.objectweb.asm.tree.*; 12 | import org.simpleframework.xml.Serializer; 13 | import org.simpleframework.xml.core.Persister; 14 | import picocli.CommandLine; 15 | 16 | import java.io.*; 17 | import java.lang.reflect.Array; 18 | import java.nio.charset.StandardCharsets; 19 | import java.nio.file.FileVisitOption; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.nio.file.Paths; 23 | import java.nio.file.attribute.FileAttribute; 24 | import java.security.GeneralSecurityException; 25 | import java.security.MessageDigest; 26 | import java.text.SimpleDateFormat; 27 | import java.util.*; 28 | import java.util.concurrent.Callable; 29 | import java.util.zip.ZipEntry; 30 | import java.util.zip.ZipFile; 31 | import java.util.zip.ZipInputStream; 32 | import java.util.zip.ZipOutputStream; 33 | 34 | 35 | public class Main 36 | { 37 | private File inputFile; 38 | static File output; 39 | 40 | 41 | 42 | public static void main(final String[] args) { 43 | System.out.println("\n"); 44 | System.out.println("JNIC Java to C translator 3.6.1"); 45 | System.out.println(" ~ (c) +Vincent Tang 2020-2024"); 46 | System.out.println("\n"); 47 | System.out.println("License: nuym (Enterprise)"); 48 | SetupManager.init(); 49 | System.exit(new CommandLine(new NativeObfuscatorRunner()).setCaseInsensitiveEnumValuesAllowed(true).execute(args)); 50 | } 51 | 52 | @CommandLine.Command(name = "Jnic", mixinStandardHelpOptions = true, version = { "Jnic Bytecode Translator" }, description = { "将.jar文件翻译成.c文件并生成输出.jar文件" }) 53 | private static class NativeObfuscatorRunner implements Callable 54 | { 55 | @CommandLine.Parameters(index = "0", description = "Jar file to transpile") 56 | private File jarFile; 57 | 58 | @CommandLine.Parameters(index = "1", description = "Output directory") 59 | private String outputDirectory; 60 | 61 | @CommandLine.Option(names = {"-c", "--config"}, defaultValue = "config.xml", 62 | description = "Config file") 63 | private File config; 64 | 65 | @CommandLine.Option(names = {"-l", "--libraries"}, description = "Directory for dependent libraries") 66 | private File librariesDirectory; 67 | 68 | @CommandLine.Option(names = {"-a", "--annotations"}, description = "Use annotations to ignore/include native obfuscation") 69 | private boolean useAnnotations; 70 | @CommandLine.Option(names = { "--plain-lib-name" }, description = { "Common library name to be used for the loader" }) 71 | private String libraryName; 72 | 73 | @Override 74 | public Integer call() throws Exception { 75 | System.out.println("Reading input jar " + this.jarFile); 76 | System.out.println("Reading configuration file " + this.config.toPath()); 77 | final StringBuilder stringBuilder = new StringBuilder(); 78 | if (Files.exists(this.config.toPath())) { 79 | try (final BufferedReader br = Files.newBufferedReader(this.config.toPath())) { 80 | String str; 81 | while ((str = br.readLine()) != null) { 82 | stringBuilder.append(str); 83 | } 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } 87 | Serializer serializer = new Persister(); 88 | Config configInfo = serializer.read(Config.class, stringBuilder.toString()); 89 | final List libs = new ArrayList(); 90 | if (this.librariesDirectory != null) { 91 | Files.walk(this.librariesDirectory.toPath(), FileVisitOption.FOLLOW_LINKS).filter(f -> f.toString().endsWith(".jar") || f.toString().endsWith(".zip")).forEach(libs::add); 92 | } 93 | if (new File(this.outputDirectory).isDirectory()) { 94 | final File outFile = new File(this.outputDirectory, this.jarFile.getName()); 95 | if (outFile.exists()) { 96 | outFile.renameTo(new File(this.outputDirectory, this.jarFile.getName() + ".BACKUP")); 97 | } 98 | } else { 99 | final File outFile = new File(this.outputDirectory); 100 | if (outFile.exists()) { 101 | outFile.renameTo(new File(this.outputDirectory + ".BACKUP")); 102 | } 103 | } 104 | //开始处理 105 | new NativeObfuscator().process(this.jarFile.toPath(), Paths.get(this.outputDirectory), configInfo, libs, this.libraryName, this.useAnnotations); 106 | return 0; 107 | } 108 | 109 | final Path path = Files.createFile(this.config.toPath(), (FileAttribute[])new FileAttribute[0]); 110 | stringBuilder.append("\n" + 111 | "\t\n" + 112 | "\t\tWINDOWS_X86_64\n" + 113 | "\t\t\n" + 114 | "\t\tMACOS_X86_64\n" + 115 | "\t\t\n" + 116 | "\t\tLINUX_X86_64\n" + 117 | "\t\t\n" + 118 | "\t\n" + 119 | "\t\n" + 120 | "\t\t\n" + 121 | "\t\tfalse\n" + 122 | "\t\t\n" + 123 | "\t\tfalse\n" + 124 | "\t\n" + 125 | "\t\n" + 126 | "\t\t\n" + 127 | "\t\t\n" + 128 | "\t\t\n" + 129 | "\t\t\n" + 130 | "\t\n" + 131 | "\t\n" + 132 | "\t\t\n" + 133 | "\t\t\n" + 134 | "\t\n" + 135 | "\n"); 136 | Files.write(path, stringBuilder.toString().getBytes(StandardCharsets.UTF_8)); 137 | System.out.println("Unable to read configuration file. Default config has been generated for you"); 138 | System.out.println("The default configuration compiles all classes and methods, which will seriously affect the running performance of the program. Please use this function with caution"); 139 | System.out.println("Please open the configuration file, configure the compiled classes and methods, and then continue to run the command"); 140 | return 0; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/ASMUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.Opcodes; 5 | import org.objectweb.asm.commons.CodeSizeEvaluator; 6 | import org.objectweb.asm.tree.*; 7 | 8 | import java.util.Arrays; 9 | 10 | public class ASMUtils implements Opcodes { 11 | 12 | private ASMUtils() { 13 | } 14 | 15 | public static class BuiltInstructions { 16 | public static InsnList getPrintln(String s) { 17 | final InsnList insnList = new InsnList(); 18 | insnList.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 19 | insnList.add(new LdcInsnNode(s)); 20 | insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 21 | return insnList; 22 | } 23 | 24 | public static InsnList getThrowNull() { 25 | final InsnList insnList = new InsnList(); 26 | insnList.add(new InsnNode(ACONST_NULL)); 27 | insnList.add(new InsnNode(ATHROW)); 28 | return insnList; 29 | } 30 | } 31 | 32 | public static boolean isClassEligibleToModify(ClassNode classNode) { 33 | return (classNode.access & ACC_INTERFACE) == 0; 34 | } 35 | 36 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") 37 | public static boolean isMethodEligibleToModify(ClassNode classNode, MethodNode methodNode) { 38 | return isClassEligibleToModify(classNode) && (methodNode.access & ACC_ABSTRACT) == 0; 39 | } 40 | 41 | public static byte[] toByteArrayDefault(ClassNode classNode) { 42 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); 43 | classNode.accept(classWriter); 44 | return classWriter.toByteArray(); 45 | } 46 | 47 | public static String getName(ClassNode classNode) { 48 | return classNode.name.replace("/", "."); 49 | } 50 | 51 | public static String getName(ClassNode classNode, FieldNode fieldNode) { 52 | return classNode.name + "." + fieldNode.name; 53 | } 54 | 55 | public static String getName(ClassNode classNode, MethodNode methodNode) { 56 | return classNode.name + "." + methodNode.name + methodNode.desc; 57 | } 58 | 59 | public static InsnList arrayToList(AbstractInsnNode[] insns) { 60 | final InsnList insnList = new InsnList(); 61 | Arrays.stream(insns).forEach(insnList::add); 62 | return insnList; 63 | } 64 | 65 | public static boolean isMethodSizeValid(MethodNode methodNode) { 66 | return getCodeSize(methodNode) <= 65536; 67 | } 68 | 69 | public static int getCodeSize(MethodNode methodNode) { 70 | CodeSizeEvaluator cse = new CodeSizeEvaluator(null); 71 | methodNode.accept(cse); 72 | return cse.getMaxSize(); 73 | } 74 | 75 | public static MethodNode findOrCreateInit(ClassNode classNode) { 76 | MethodNode clinit = findMethod(classNode, "", "()V"); 77 | if (clinit == null) { 78 | clinit = new MethodNode(ACC_PUBLIC, "", "()V", null, null); 79 | clinit.instructions.add(new InsnNode(RETURN)); 80 | classNode.methods.add(clinit); 81 | } 82 | return clinit; 83 | } 84 | 85 | public static MethodNode findOrCreateClinit(ClassNode classNode) { 86 | MethodNode clinit = findMethod(classNode, "", "()V"); 87 | if (clinit == null) { 88 | clinit = new MethodNode(ACC_STATIC, "", "()V", null, null); 89 | clinit.instructions.add(new InsnNode(RETURN)); 90 | classNode.methods.add(clinit); 91 | } 92 | return clinit; 93 | } 94 | 95 | public static MethodNode findMethod(ClassNode classNode, String name, String desc) { 96 | return classNode.methods 97 | .stream() 98 | .filter(methodNode -> name.equals(methodNode.name) && desc.equals(methodNode.desc)) 99 | .findAny() 100 | .orElse(null); 101 | } 102 | 103 | public static boolean isInvokeMethod(AbstractInsnNode insn, boolean includeInvokeDynamic) { 104 | return insn.getOpcode() >= INVOKEVIRTUAL && (includeInvokeDynamic ? insn.getOpcode() <= INVOKEDYNAMIC : insn.getOpcode() < INVOKEDYNAMIC); 105 | } 106 | 107 | public static boolean isFieldInsn(AbstractInsnNode insn) { 108 | return insn.getOpcode() >= GETSTATIC && insn.getOpcode() <= PUTFIELD; 109 | } 110 | 111 | public static boolean isIf(AbstractInsnNode insn) { 112 | int op = insn.getOpcode(); 113 | return (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL; 114 | } 115 | 116 | public static AbstractInsnNode pushLong(long value) { 117 | if (value == 0) return new InsnNode(LCONST_0); 118 | else if (value == 1) return new InsnNode(LCONST_1); 119 | else return new LdcInsnNode(value); 120 | } 121 | 122 | public static boolean isPushLong(AbstractInsnNode insn) { 123 | try { 124 | getPushedLong(insn); 125 | return true; 126 | } catch (IllegalArgumentException e) { 127 | return false; 128 | } 129 | } 130 | 131 | public static long getPushedLong(AbstractInsnNode insn) throws IllegalArgumentException { 132 | IllegalArgumentException ex = new IllegalArgumentException("Insn is not a push long instruction"); 133 | switch (insn.getOpcode()) { 134 | case LCONST_0: 135 | return 0; 136 | case LCONST_1: 137 | return 1; 138 | case LDC: { 139 | Object cst = ((LdcInsnNode) insn).cst; 140 | if (cst instanceof Long) 141 | return (Long) cst; 142 | throw ex; 143 | } 144 | default: 145 | throw ex; 146 | } 147 | } 148 | 149 | public static AbstractInsnNode pushInt(int value) { 150 | if (value >= -1 && value <= 5) { 151 | return new InsnNode(ICONST_0 + value); 152 | } 153 | if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 154 | return new IntInsnNode(BIPUSH, value); 155 | } 156 | if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 157 | return new IntInsnNode(SIPUSH, value); 158 | } 159 | return new LdcInsnNode(value); 160 | } 161 | 162 | public static boolean isPushInt(AbstractInsnNode insn) { 163 | try { 164 | getPushedInt(insn); 165 | return true; 166 | } catch (IllegalArgumentException e) { 167 | return false; 168 | } 169 | } 170 | 171 | public static int getPushedInt(AbstractInsnNode insn) throws IllegalArgumentException { 172 | IllegalArgumentException ex = new IllegalArgumentException("Insn is not a push int instruction"); 173 | int op = insn.getOpcode(); 174 | switch (op) { 175 | case ICONST_M1: 176 | case ICONST_0: 177 | case ICONST_1: 178 | case ICONST_2: 179 | case ICONST_3: 180 | case ICONST_4: 181 | case ICONST_5: 182 | return (op - ICONST_0); 183 | case BIPUSH: 184 | case SIPUSH: 185 | return ((IntInsnNode) insn).operand; 186 | case LDC: { 187 | Object cst = ((LdcInsnNode) insn).cst; 188 | if (cst instanceof Integer) 189 | return (int) cst; 190 | throw ex; 191 | } 192 | default: 193 | throw ex; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.StringTokenizer; 7 | 8 | public class StringUtils 9 | { 10 | private static final String[] EMPTY_STRING_ARRAY; 11 | 12 | public static String[] tokenizeToStringArray(final String str, final String delimiters, final boolean trimTokens, final boolean ignoreEmptyTokens) { 13 | if (str == null) { 14 | return StringUtils.EMPTY_STRING_ARRAY; 15 | } 16 | final StringTokenizer st = new StringTokenizer(str, delimiters); 17 | final List tokens = new ArrayList(); 18 | while (st.hasMoreTokens()) { 19 | String token = st.nextToken(); 20 | if (trimTokens) { 21 | token = token.trim(); 22 | } 23 | if (!ignoreEmptyTokens || token.length() > 0) { 24 | tokens.add(token); 25 | } 26 | } 27 | return toStringArray(tokens); 28 | } 29 | 30 | public static boolean hasText(final CharSequence str) { 31 | return str != null && str.length() > 0 && containsText(str); 32 | } 33 | 34 | private static boolean containsText(final CharSequence str) { 35 | for (int strLen = str.length(), i = 0; i < strLen; ++i) { 36 | if (!Character.isWhitespace(str.charAt(i))) { 37 | return true; 38 | } 39 | } 40 | return false; 41 | } 42 | public static String replaceColor(final String string, final String replace, final String color) { 43 | return string.replace(replace, color + replace + "\033[0m" /* reset color ansi*/); 44 | } 45 | public static String[] toStringArray(final Collection collection) { 46 | return collection.isEmpty() ? StringUtils.EMPTY_STRING_ARRAY : collection.toArray(StringUtils.EMPTY_STRING_ARRAY); 47 | } 48 | 49 | public static boolean equals(final CharSequence cs1, final CharSequence cs2) { 50 | if (cs1 == cs2) { 51 | return true; 52 | } 53 | if (cs1 == null || cs2 == null) { 54 | return false; 55 | } 56 | if (cs1.length() != cs2.length()) { 57 | return false; 58 | } 59 | if (cs1 instanceof String && cs2 instanceof String) { 60 | return cs1.equals(cs2); 61 | } 62 | for (int length = cs1.length(), i = 0; i < length; ++i) { 63 | if (cs1.charAt(i) != cs2.charAt(i)) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | public static boolean contains(final CharSequence seq, final CharSequence searchSeq) { 71 | return seq != null && searchSeq != null && indexOf(seq, searchSeq, 0) >= 0; 72 | } 73 | 74 | static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) { 75 | if (cs instanceof String) { 76 | return ((String)cs).indexOf(searchChar.toString(), start); 77 | } 78 | if (cs instanceof StringBuilder) { 79 | return ((StringBuilder)cs).indexOf(searchChar.toString(), start); 80 | } 81 | return (cs instanceof StringBuffer) ? ((StringBuffer)cs).indexOf(searchChar.toString(), start) : cs.toString().indexOf(searchChar.toString(), start); 82 | } 83 | 84 | public static boolean isBlank(final CharSequence cs) { 85 | final int strLen = length(cs); 86 | if (strLen == 0) { 87 | return true; 88 | } 89 | for (int i = 0; i < strLen; ++i) { 90 | if (!Character.isWhitespace(cs.charAt(i))) { 91 | return false; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | public static int length(final CharSequence cs) { 98 | return (cs == null) ? 0 : cs.length(); 99 | } 100 | 101 | public static String trim(final String str) { 102 | return (str == null) ? null : str.trim(); 103 | } 104 | 105 | public static boolean isNotEmpty(final CharSequence cs) { 106 | return !isEmpty(cs); 107 | } 108 | 109 | public static boolean isEmpty(final CharSequence cs) { 110 | return cs == null || cs.length() == 0; 111 | } 112 | 113 | public static String substringBetween(final String str, final String open, final String close) { 114 | if (!allNotNull(str, open, close)) { 115 | return null; 116 | } 117 | final int start = str.indexOf(open); 118 | if (start != -1) { 119 | final int end = str.indexOf(close, start + open.length()); 120 | if (end != -1) { 121 | return str.substring(start + open.length(), end); 122 | } 123 | } 124 | return null; 125 | } 126 | 127 | public static boolean allNotNull(final String... values) { 128 | if (values == null) { 129 | return false; 130 | } 131 | for (final String val : values) { 132 | if (val == null) { 133 | return false; 134 | } 135 | } 136 | return true; 137 | } 138 | 139 | public static String replace(final String text, final String searchString, final String replacement) { 140 | return replace(text, searchString, replacement, -1); 141 | } 142 | 143 | public static String replace(final String text, final String searchString, final String replacement, final int max) { 144 | return replace(text, searchString, replacement, max, false); 145 | } 146 | 147 | private static String replace(final String text, String searchString, final String replacement, int max, final boolean ignoreCase) { 148 | if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { 149 | return text; 150 | } 151 | if (ignoreCase) { 152 | searchString = searchString.toLowerCase(); 153 | } 154 | int start = 0; 155 | int end = ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start); 156 | if (end == -1) { 157 | return text; 158 | } 159 | final int replLength = searchString.length(); 160 | int increase = Math.max(replacement.length() - replLength, 0); 161 | increase *= ((max < 0) ? 16 : Math.min(max, 64)); 162 | final StringBuilder buf = new StringBuilder(text.length() + increase); 163 | while (end != -1) { 164 | buf.append(text, start, end).append(replacement); 165 | start = end + replLength; 166 | if (--max == 0) { 167 | break; 168 | } 169 | end = (ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start)); 170 | } 171 | buf.append(text, start, text.length()); 172 | return buf.toString(); 173 | } 174 | 175 | public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { 176 | return indexOfIgnoreCase(str, searchStr, 0); 177 | } 178 | 179 | public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { 180 | if (str == null || searchStr == null) { 181 | return -1; 182 | } 183 | if (startPos < 0) { 184 | startPos = 0; 185 | } 186 | final int endLimit = str.length() - searchStr.length() + 1; 187 | if (startPos > endLimit) { 188 | return -1; 189 | } 190 | if (searchStr.length() == 0) { 191 | return startPos; 192 | } 193 | for (int i = startPos; i < endLimit; ++i) { 194 | if (regionMatches(str, true, i, searchStr, 0, searchStr.length())) { 195 | return i; 196 | } 197 | } 198 | return -1; 199 | } 200 | 201 | static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, final int length) { 202 | if (cs instanceof String && substring instanceof String) { 203 | return ((String)cs).regionMatches(ignoreCase, thisStart, (String)substring, start, length); 204 | } 205 | int index1 = thisStart; 206 | int index2 = start; 207 | int tmpLen = length; 208 | final int srcLen = cs.length() - thisStart; 209 | final int otherLen = substring.length() - start; 210 | if (thisStart < 0 || start < 0 || length < 0) { 211 | return false; 212 | } 213 | if (srcLen >= length && otherLen >= length) { 214 | while (tmpLen-- > 0) { 215 | final char c1 = cs.charAt(index1++); 216 | final char c2 = substring.charAt(index2++); 217 | if (c1 != c2) { 218 | if (!ignoreCase) { 219 | return false; 220 | } 221 | final char u1 = Character.toUpperCase(c1); 222 | final char u2 = Character.toUpperCase(c2); 223 | if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) { 224 | return false; 225 | } 226 | continue; 227 | } 228 | } 229 | return true; 230 | } 231 | return false; 232 | } 233 | 234 | static { 235 | EMPTY_STRING_ARRAY = new String[0]; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/utils/DecryptorClass.java: -------------------------------------------------------------------------------- 1 | package cc.nuym.jnic.utils; 2 | 3 | import org.objectweb.asm.*; 4 | public class DecryptorClass implements Opcodes { 5 | private String className; 6 | private String fieldName; 7 | private String fieldName2; 8 | private String methodName; 9 | 10 | public DecryptorClass(String className, String fieldName, String fieldName2, String methodName) { 11 | this.className = className; 12 | this.fieldName = fieldName; 13 | this.fieldName2 = fieldName2; 14 | this.methodName = methodName; 15 | } 16 | 17 | public byte[] getBytes() { 18 | ClassWriter cw = new ClassWriter(0); 19 | FieldVisitor fv; 20 | MethodVisitor mv; 21 | 22 | cw.visit(52, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null); 23 | 24 | { 25 | fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, fieldName, "[I", null, null); 26 | fv.visitEnd(); 27 | } 28 | { 29 | fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, fieldName2, "I", null, null); 30 | fv.visitEnd(); 31 | } 32 | { 33 | mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); 34 | mv.visitCode(); 35 | Label l0 = new Label(); 36 | mv.visitLabel(l0); 37 | mv.visitVarInsn(ALOAD, 0); 38 | mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); 39 | mv.visitInsn(RETURN); 40 | Label l1 = new Label(); 41 | mv.visitLabel(l1); 42 | mv.visitMaxs(1, 1); 43 | mv.visitEnd(); 44 | } 45 | { 46 | mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, methodName, "(Ljava/lang/Object;)Ljava/lang/String;", null, new String[]{"java/lang/Throwable"}); 47 | mv.visitCode(); 48 | Label l0 = new Label(); 49 | mv.visitLabel(l0); 50 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", false); 51 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "getStackTrace", "()[Ljava/lang/StackTraceElement;", false); 52 | mv.visitVarInsn(ASTORE, 1); 53 | Label l1 = new Label(); 54 | mv.visitLabel(l1); 55 | mv.visitMethodInsn(INVOKESTATIC, "sun/misc/SharedSecrets", "getJavaLangAccess", "()Lsun/misc/JavaLangAccess;", false); 56 | mv.visitVarInsn(ALOAD, 1); 57 | mv.visitFieldInsn(GETSTATIC, className, fieldName, "[I"); 58 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 59 | mv.visitInsn(ICONST_1); 60 | mv.visitInsn(ISUB); 61 | mv.visitInsn(IALOAD); 62 | mv.visitInsn(AALOAD); 63 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StackTraceElement", "getClassName", "()Ljava/lang/String;", false); 64 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); 65 | mv.visitMethodInsn(INVOKEINTERFACE, "sun/misc/JavaLangAccess", "getConstantPool", "(Ljava/lang/Class;)Lsun/reflect/ConstantPool;", true); 66 | mv.visitMethodInsn(INVOKEVIRTUAL, "sun/reflect/ConstantPool", "getSize", "()I", false); 67 | mv.visitVarInsn(ISTORE, 2); 68 | Label l2 = new Label(); 69 | mv.visitLabel(l2); 70 | mv.visitVarInsn(ALOAD, 1); 71 | mv.visitFieldInsn(GETSTATIC, className, fieldName, "[I"); 72 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 73 | mv.visitInsn(ICONST_1); 74 | mv.visitInsn(ISUB); 75 | mv.visitInsn(IALOAD); 76 | mv.visitInsn(AALOAD); 77 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StackTraceElement", "getClassName", "()Ljava/lang/String;", false); 78 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); 79 | mv.visitVarInsn(ISTORE, 3); 80 | Label l3 = new Label(); 81 | mv.visitLabel(l3); 82 | mv.visitVarInsn(ALOAD, 1); 83 | mv.visitFieldInsn(GETSTATIC, className, fieldName, "[I"); 84 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 85 | mv.visitInsn(ICONST_1); 86 | mv.visitInsn(ISUB); 87 | mv.visitInsn(IALOAD); 88 | mv.visitInsn(AALOAD); 89 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StackTraceElement", "getMethodName", "()Ljava/lang/String;", false); 90 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); 91 | mv.visitVarInsn(ISTORE, 4); 92 | Label l4 = new Label(); 93 | mv.visitLabel(l4); 94 | mv.visitVarInsn(ALOAD, 0); 95 | mv.visitTypeInsn(CHECKCAST, "java/lang/String"); 96 | mv.visitVarInsn(ASTORE, 5); 97 | Label l5 = new Label(); 98 | mv.visitLabel(l5); 99 | mv.visitVarInsn(ALOAD, 5); 100 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 101 | mv.visitIntInsn(NEWARRAY, T_CHAR); 102 | mv.visitVarInsn(ASTORE, 6); 103 | Label l6 = new Label(); 104 | mv.visitLabel(l6); 105 | mv.visitVarInsn(ALOAD, 5); 106 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "toCharArray", "()[C", false); 107 | mv.visitVarInsn(ASTORE, 7); 108 | Label l7 = new Label(); 109 | mv.visitLabel(l7); 110 | mv.visitVarInsn(ALOAD, 5); 111 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I", false); 112 | mv.visitVarInsn(ISTORE, 8); 113 | Label l8 = new Label(); 114 | mv.visitLabel(l8); 115 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 116 | mv.visitFieldInsn(GETSTATIC, className, fieldName, "[I"); 117 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 118 | mv.visitInsn(ICONST_1); 119 | mv.visitInsn(ISUB); 120 | mv.visitInsn(IALOAD); 121 | mv.visitInsn(ISHR); 122 | mv.visitFieldInsn(GETSTATIC, className, fieldName2, "I"); 123 | mv.visitInsn(ISHR); 124 | mv.visitVarInsn(ISTORE, 9); 125 | Label l9 = new Label(); 126 | mv.visitLabel(l9); 127 | mv.visitFrame(Opcodes.F_FULL, 10, new Object[]{"java/lang/Object", "[Ljava/lang/StackTraceElement;", Opcodes.INTEGER, Opcodes.INTEGER, Opcodes.INTEGER, "java/lang/String", "[C", "[C", Opcodes.INTEGER, Opcodes.INTEGER}, 0, new Object[]{}); 128 | mv.visitVarInsn(ILOAD, 9); 129 | mv.visitVarInsn(ILOAD, 8); 130 | Label l10 = new Label(); 131 | mv.visitJumpInsn(IF_ICMPLT, l10); 132 | Label l11 = new Label(); 133 | mv.visitLabel(l11); 134 | Label l12 = new Label(); 135 | mv.visitJumpInsn(GOTO, l12); 136 | mv.visitLabel(l10); 137 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 138 | mv.visitVarInsn(ILOAD, 9); 139 | mv.visitInsn(ICONST_2); 140 | mv.visitInsn(IREM); 141 | Label l13 = new Label(); 142 | Label l14 = new Label(); 143 | Label l15 = new Label(); 144 | mv.visitLookupSwitchInsn(l15, new int[]{0, 1}, new Label[]{l13, l14}); 145 | mv.visitLabel(l13); 146 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 147 | mv.visitVarInsn(ALOAD, 6); 148 | mv.visitVarInsn(ILOAD, 9); 149 | mv.visitVarInsn(ALOAD, 7); 150 | mv.visitVarInsn(ILOAD, 9); 151 | mv.visitInsn(CALOAD); 152 | mv.visitVarInsn(ILOAD, 3); 153 | mv.visitInsn(IXOR); 154 | mv.visitVarInsn(ILOAD, 2); 155 | mv.visitInsn(IXOR); 156 | mv.visitInsn(I2C); 157 | mv.visitInsn(CASTORE); 158 | Label l16 = new Label(); 159 | mv.visitLabel(l16); 160 | mv.visitJumpInsn(GOTO, l15); 161 | mv.visitLabel(l14); 162 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 163 | mv.visitVarInsn(ALOAD, 6); 164 | mv.visitVarInsn(ILOAD, 9); 165 | mv.visitVarInsn(ALOAD, 7); 166 | mv.visitVarInsn(ILOAD, 9); 167 | mv.visitInsn(CALOAD); 168 | mv.visitVarInsn(ILOAD, 4); 169 | mv.visitInsn(IXOR); 170 | mv.visitVarInsn(ILOAD, 2); 171 | mv.visitInsn(IXOR); 172 | mv.visitInsn(I2C); 173 | mv.visitInsn(CASTORE); 174 | mv.visitLabel(l15); 175 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 176 | mv.visitIincInsn(9, 1); 177 | mv.visitJumpInsn(GOTO, l9); 178 | mv.visitLabel(l12); 179 | mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 180 | mv.visitTypeInsn(NEW, "java/lang/String"); 181 | mv.visitInsn(DUP); 182 | mv.visitVarInsn(ALOAD, 6); 183 | mv.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "", "([C)V", false); 184 | mv.visitInsn(ARETURN); 185 | Label l17 = new Label(); 186 | mv.visitLabel(l17); 187 | mv.visitMaxs(5, 10); 188 | mv.visitEnd(); 189 | } 190 | { 191 | mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); 192 | mv.visitCode(); 193 | Label l0 = new Label(); 194 | mv.visitLabel(l0); 195 | mv.visitInsn(ICONST_1); 196 | mv.visitIntInsn(NEWARRAY, T_INT); 197 | mv.visitFieldInsn(PUTSTATIC, className, fieldName, "[I"); 198 | Label l1 = new Label(); 199 | mv.visitLabel(l1); 200 | mv.visitInsn(ICONST_1); 201 | mv.visitVarInsn(ISTORE, 0); 202 | mv.visitVarInsn(ILOAD, 0); 203 | mv.visitFieldInsn(PUTSTATIC, className, fieldName2, "I"); 204 | mv.visitFieldInsn(GETSTATIC, className, fieldName, "[I"); 205 | mv.visitInsn(ICONST_0); 206 | mv.visitInsn(ICONST_2); 207 | mv.visitInsn(IASTORE); 208 | Label l2 = new Label(); 209 | mv.visitLabel(l2); 210 | mv.visitInsn(RETURN); 211 | mv.visitMaxs(3, 1); 212 | mv.visitEnd(); 213 | } 214 | cw.visitEnd(); 215 | 216 | return cw.toByteArray(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/cc/nuym/jnic/instructions/MethodHandler.java: -------------------------------------------------------------------------------- 1 | // rebuild 2 | package cc.nuym.jnic.instructions; 3 | 4 | import cc.nuym.jnic.utils.MethodContext; 5 | import cc.nuym.jnic.utils.Util; 6 | import cc.nuym.jnic.cache.CachedClassInfo; 7 | import cc.nuym.jnic.cache.CachedFieldInfo; 8 | import cc.nuym.jnic.cache.CachedMethodInfo; 9 | import org.objectweb.asm.Type; 10 | import org.objectweb.asm.tree.MethodInsnNode; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.stream.Stream; 16 | 17 | public class MethodHandler 18 | extends GenericInstructionHandler { 19 | private static Type simplifyType(Type type) { 20 | switch (type.getSort()) { 21 | case Type.OBJECT: 22 | case Type.ARRAY: 23 | return Type.getObjectType("java/lang/Object"); 24 | case Type.METHOD: 25 | throw new RuntimeException(); 26 | } 27 | return type; 28 | } 29 | 30 | private static String simplifyDesc(String desc) { 31 | return Type.getMethodType(MethodHandler.simplifyType(Type.getReturnType(desc)), (Type[])Arrays.stream(Type.getArgumentTypes(desc)).map(MethodHandler::simplifyType).toArray(Type[]::new)).getDescriptor(); 32 | } 33 | 34 | @Override 35 | protected void process(MethodContext context, MethodInsnNode node) { 36 | if (node.owner.equals("java/lang/invoke/MethodHandle") && (node.name.equals("invokeExact") || node.name.equals("invoke")) && node.getOpcode() == 182) { 37 | String methodDesc = MethodHandler.simplifyDesc(Type.getMethodType(Type.getReturnType(node.desc), (Type[])Stream.concat(Arrays.stream(new Type[]{Type.getObjectType("java/lang/invoke/MethodHandle")}), Arrays.stream(Type.getArgumentTypes(node.desc))).toArray(Type[]::new)).getDescriptor()); 38 | Type[] methodArguments = Type.getArgumentTypes(methodDesc); 39 | methodArguments[0] = Type.getObjectType("java/lang/invoke/MethodHandle"); 40 | methodDesc = Type.getMethodDescriptor(Type.getReturnType(methodDesc), methodArguments); 41 | String mhDesc = MethodHandler.simplifyDesc(node.desc); 42 | context.output.append("temp0.l = (*env)->NewObjectArray(env, " + Type.getArgumentTypes(mhDesc).length + ", c_" + context.getCachedClasses().getClass("java/lang/Object").getId() + "_(env)->clazz, NULL);\n"); 43 | for (int i = 0; i < Type.getArgumentTypes(mhDesc).length; ++i) { 44 | Type argumentType = Type.getArgumentTypes(mhDesc)[i]; 45 | switch (argumentType.getSort()) { 46 | case Type.BOOLEAN: 47 | case Type.CHAR: 48 | case Type.BYTE: 49 | case Type.INT:{ 50 | CachedClassInfo integer = context.getCachedClasses().getClass("java/lang/Integer"); 51 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, ").append(i).append(", (*env)->CallStaticObjectMethod(env, c_").append(integer.getId()).append("_(env)->clazz, c_").append(integer.getId()).append("_(env)->method_").append(integer.getCachedMethodId(new CachedMethodInfo("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", true))).append(", cstack").append(1 + i).append(".l));\n"); 52 | break; 53 | } 54 | case Type.FLOAT:{ 55 | CachedClassInfo jfloat = context.getCachedClasses().getClass("java/lang/Float"); 56 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, ").append(i).append(", (*env)->CallStaticObjectMethod(env, c_").append(jfloat.getId()).append("_(env)->clazz, c_").append(jfloat.getId()).append("_(env)->method_").append(jfloat.getCachedMethodId(new CachedMethodInfo("java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", true))).append(", cstack").append(1 + i).append(".l));\n"); 57 | break; 58 | } 59 | case Type.LONG: { 60 | CachedClassInfo jlong = context.getCachedClasses().getClass("java/lang/Long"); 61 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, ").append(i).append(", (*env)->CallStaticObjectMethod(env, c_").append(jlong.getId()).append("_(env)->clazz, c_").append(jlong.getId()).append("_(env)->method_").append(jlong.getCachedMethodId(new CachedMethodInfo("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", true))).append(", cstack").append(1 + i).append(".l));\n"); 62 | break; 63 | } 64 | case Type.DOUBLE: { 65 | CachedClassInfo jdouble = context.getCachedClasses().getClass("java/lang/Double"); 66 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, ").append(i).append(", (*env)->CallStaticObjectMethod(env, c_").append(jdouble.getId()).append("_(env)->clazz, c_").append(jdouble.getId()).append("_(env)->method_").append(jdouble.getCachedMethodId(new CachedMethodInfo("java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", true))).append(", cstack").append(1 + i).append(".l));\n"); 67 | break; 68 | } 69 | case Type.ARRAY: 70 | case Type.OBJECT:{ 71 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, " + i + ", cstack" + (1 + i) + ".l);\n"); 72 | break; 73 | } 74 | case Type.METHOD:{ 75 | CachedClassInfo clazz = context.getCachedClasses().getClass(context.clazz.name); 76 | CachedClassInfo javaClass = context.getCachedClasses().getClass("java/lang/Class"); 77 | CachedClassInfo methodType = context.getCachedClasses().getClass("java/lang/invoke/MethodType"); 78 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, " + i + ", (*env)->CallStaticObjectMethod(env, /*java/lang/invoke/MethodType*/c_" + methodType.getId() + "_(env)->clazz, /*fromMethodDescriptorString*/c_" + methodType.getId() + "_(env)->method_" + methodType.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", true)) + ", /*" + argumentType + "*/(*env)->NewString(env, (unsigned short[]) {" + Util.utf82unicode(argumentType.toString()) + "}, " + argumentType.toString().length() + "), (*env)->CallObjectMethod(env,/*" + context.clazz.name + "*/c_" + clazz.getId() + "_(env)->clazz, /*java/lang/Class.getClassLoader*/c_" + javaClass.getId() + "_(env)->method_" + javaClass.getCachedMethodId(new CachedMethodInfo("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false)) + ")));\n"); 79 | break; 80 | } 81 | default: { 82 | context.output.append("(*env)->SetObjectArrayElement(env, temp0.l, " + i + ", cstack" + (1 + i) + ".l);\n"); 83 | } 84 | } 85 | } 86 | CachedClassInfo methodHandle = context.getCachedClasses().getClass("java/lang/invoke/MethodHandle"); 87 | if (Type.getReturnType(mhDesc).getSize() == 0) { 88 | context.output.append("(*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l);\n"); 89 | } else { 90 | Type returnType = Type.getReturnType(mhDesc); 91 | switch (returnType.getSort()) { 92 | case Type.BOOLEAN: 93 | CachedClassInfo jbool = context.getCachedClasses().getClass("java/lang/Boolean"); 94 | context.output.append("cstack0.z = (*env)->CallBooleanMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jbool.getId() + "_(env)->method_" + jbool.getCachedMethodId(new CachedMethodInfo("java/lang/Boolean", "booleanValue", "()Z", false)) + ");\n"); 95 | break; 96 | case Type.CHAR: 97 | CachedClassInfo jchar = context.getCachedClasses().getClass("java/lang/Character"); 98 | context.output.append("cstack0.c = (*env)->CallCharMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jchar.getId() + "_(env)->method_" + jchar.getCachedMethodId(new CachedMethodInfo("java/lang/Character", "charValue", "()C", false)) + ");\n"); 99 | break; 100 | case Type.BYTE: 101 | CachedClassInfo jbyte = context.getCachedClasses().getClass("java/lang/Byte"); 102 | context.output.append("cstack0.b = (*env)->CallByteMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jbyte.getId() + "_(env)->method_" + jbyte.getCachedMethodId(new CachedMethodInfo("java/lang/Byte", "byteValue", "()B", false)) + ");\n"); 103 | break; 104 | case Type.INT: 105 | CachedClassInfo jint = context.getCachedClasses().getClass("java/lang/Integer"); 106 | context.output.append("cstack0.i = (*env)->CallIntMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jint.getId() + "_(env)->method_" + jint.getCachedMethodId(new CachedMethodInfo("java/lang/Integer", "intValue", "()I", false)) + ");\n"); 107 | break; 108 | case Type.FLOAT: 109 | CachedClassInfo jfloat = context.getCachedClasses().getClass("java/lang/Float"); 110 | context.output.append("cstack0.f = (*env)->CallFloatMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jfloat.getId() + "_(env)->method_" + jfloat.getCachedMethodId(new CachedMethodInfo("java/lang/Float", "floatValue", "()F", false)) + ");\n"); 111 | break; 112 | case Type.LONG: 113 | CachedClassInfo jlong = context.getCachedClasses().getClass("java/lang/Long"); 114 | context.output.append("cstack0.j = (*env)->CallLongMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jlong.getId() + "_(env)->method_" + jlong.getCachedMethodId(new CachedMethodInfo("java/lang/Long", "longValue", "()J", false)) + ");\n"); 115 | break; 116 | case Type.DOUBLE: 117 | CachedClassInfo jdouble = context.getCachedClasses().getClass("java/lang/Double"); 118 | context.output.append("cstack0.d = (*env)->CallDoubleMethod(env, (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l),c_" + jdouble.getId() + "_(env)->method_" + jdouble.getCachedMethodId(new CachedMethodInfo("java/lang/Double", "doubleValue", "()D", false)) + ");\n"); 119 | break; 120 | case Type.METHOD: 121 | break; 122 | default: 123 | context.output.append("cstack0.l = (*env)->CallObjectMethod(env, cstack0.l, c_" + methodHandle.getId() + "_(env)->method_" + methodHandle.getCachedMethodId(new CachedMethodInfo("java/lang/invoke/MethodHandle", "invokeWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false)) + ", temp0.l);\n"); 124 | break; 125 | } 126 | } 127 | context.output.append((String)this.props.get("trycatchhandler")); 128 | return; 129 | } 130 | 131 | this.props.put("class_ptr", "c_" + context.getCachedClasses().getId(node.owner) + "_"); 132 | CachedClassInfo classInfo = context.getCachedClasses().getClass(node.owner); 133 | List cachedFields = classInfo.getCachedFields(); 134 | for (int i = 0; i < cachedFields.size(); ++i) { 135 | CachedFieldInfo fieldNode = cachedFields.get(i); 136 | if (!fieldNode.getName().equals(node.name)) continue; 137 | this.props.put("field_id", "id_" + i); 138 | } 139 | Type returnType = Type.getReturnType(node.desc); 140 | Type[] args = Type.getArgumentTypes(node.desc); 141 | this.instructionName = this.instructionName + "_" + returnType.getSort(); 142 | StringBuilder argsBuilder = new StringBuilder(); 143 | ArrayList argOffsets = new ArrayList(); 144 | int stackOffset = context.stackPointer; 145 | for (Type argType : args) { 146 | stackOffset -= argType.getSize(); 147 | } 148 | int argumentOffset = stackOffset; 149 | for (Type argType : args) { 150 | argOffsets.add(argumentOffset); 151 | argumentOffset += argType.getSize(); 152 | } 153 | boolean isStatic = node.getOpcode() == 184; 154 | int objectOffset = isStatic ? 0 : 1; 155 | for (int i = 0; i < argOffsets.size(); ++i) { 156 | argsBuilder.append(", ").append(context.getSnippets().getSnippet("INVOKE_ARG_" + args[i].getSort(), Util.createMap("index", argOffsets.get(i)))); 157 | } 158 | this.props.put("objectstackindex", String.valueOf(stackOffset - objectOffset)); 159 | this.props.put("returnstackindex", String.valueOf(stackOffset - objectOffset)); 160 | List cachedMethods = classInfo.getCachedMethods(); 161 | for (int i = 0; i < cachedMethods.size(); ++i) { 162 | CachedMethodInfo cachedMethodInfo = cachedMethods.get(i); 163 | if (!cachedMethodInfo.getName().equals(node.name) || !cachedMethodInfo.getDesc().equals(node.desc)) continue; 164 | this.props.put("methodid", "method_" + i); 165 | } 166 | if (this.props.get("methodid") == null) { 167 | CachedMethodInfo methodInfo = new CachedMethodInfo(node.owner, node.name, node.desc, isStatic); 168 | methodInfo.setId(cachedMethods.size()); 169 | cachedMethods.add(methodInfo); 170 | this.props.put("methodid", "method_" + (cachedMethods.size() - 1)); 171 | } 172 | this.props.put("args", argsBuilder.toString()); 173 | } 174 | 175 | @Override 176 | public String insnToString(MethodContext context, MethodInsnNode node) { 177 | return String.format("%s %s.%s%s", Util.getOpcodeString(node.getOpcode()), node.owner, node.name, node.desc); 178 | } 179 | 180 | @Override 181 | public int getNewStackPointer(MethodInsnNode node, int currentStackPointer) { 182 | if (node.getOpcode() != 184) { 183 | --currentStackPointer; 184 | } 185 | return currentStackPointer - Arrays.stream(Type.getArgumentTypes(node.desc)).mapToInt(Type::getSize).sum() + Type.getReturnType(node.desc).getSize(); 186 | } 187 | } 188 | --------------------------------------------------------------------------------