├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ └── ClientJarOnly.class.repacker │ └── java │ │ └── com │ │ └── fox2code │ │ └── repacker │ │ ├── rebuild │ │ ├── ClassData.java │ │ └── ClassDataProvider.java │ │ ├── layout │ │ ├── DirLayout.java │ │ ├── FlatDirLayout.java │ │ └── MavenDirLayout.java │ │ ├── ClientJarOnly.java │ │ ├── utils │ │ ├── RepackException.java │ │ ├── ConsoleColors.java │ │ └── Utils.java │ │ ├── patchers │ │ ├── PostPatcher.java │ │ ├── PostPatcherStack.java │ │ ├── ClientAnnotationPatcher.java │ │ ├── Mapping.java │ │ └── BytecodeFixer.java │ │ ├── Main.java │ │ └── Repacker.java └── test │ └── java │ └── com │ └── fox2code │ └── repacker │ └── tests │ └── Tests.java ├── .gitignore ├── README.md ├── LICENSE ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Repacker' 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fox2Code/Repacker/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/ClientJarOnly.class.repacker: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fox2Code/Repacker/HEAD/src/main/resources/ClientJarOnly.class.repacker -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 29 13:40:35 CEST 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/rebuild/ClassData.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.rebuild; 2 | 3 | public interface ClassData { 4 | String getName(); 5 | ClassData getSuperclass(); 6 | ClassData[] getInterfaces(); 7 | boolean isAssignableFrom(ClassData clData); 8 | boolean isInterface(); 9 | boolean isFinal(); 10 | boolean isPublic(); 11 | boolean isCustom(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/layout/DirLayout.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.layout; 2 | 3 | import java.io.File; 4 | 5 | public interface DirLayout { 6 | File getIndexCache(); 7 | File getVersionIndexFile(String version); 8 | File getMappingFile(String version, boolean client); 9 | File getMinecraftFile(String version, boolean client); 10 | File getMinecraftRepackFile(String version, boolean client); 11 | void generateDirsFor(String version); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/ClientJarOnly.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.FIELD,ElementType.TYPE,ElementType.ANNOTATION_TYPE}) 9 | @Retention(RetentionPolicy.CLASS) 10 | public @interface ClientJarOnly { 11 | // TODO Automacily move the class in /repacker.ClientJarOnly.class.res on build 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/utils/RepackException.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.utils; 2 | 3 | import java.io.IOException; 4 | 5 | public class RepackException extends IOException { 6 | public RepackException() { 7 | super(); 8 | } 9 | 10 | public RepackException(String message) { 11 | super(ConsoleColors.RED_BOLD + message + ConsoleColors.RESET); 12 | } 13 | 14 | public RepackException(String message, Throwable cause) { 15 | super(ConsoleColors.RED_BOLD + message + ConsoleColors.RESET, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .metadata 2 | bin/ 3 | tmp/ 4 | *.tmp 5 | *.bak 6 | *.swp 7 | *~.nib 8 | local.properties 9 | .settings/ 10 | .loadpath 11 | .recommenders 12 | .externalToolBuilders/ 13 | *.launch 14 | *.pydevproject 15 | .cproject 16 | .autotools 17 | .classpath 18 | .project 19 | .factorypath 20 | .buildpath 21 | .target 22 | .tern-project 23 | .texlipse 24 | .springBeans 25 | .recommenders/ 26 | .apt_generated/ 27 | .cache-main 28 | .scala_dependencies 29 | .worksheet 30 | .gradle 31 | build/ 32 | *.iws 33 | out/ 34 | .idea_modules/ 35 | atlassian-ide-plugin.xml 36 | com_crashlytics_export_strings.xml 37 | crashlytics.properties 38 | crashlytics-build.properties 39 | fabric.properties 40 | .idea/ 41 | hs_err_pid* 42 | 43 | /dd/ 44 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/patchers/PostPatcher.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.patchers; 2 | 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.Opcodes; 5 | 6 | import java.util.Map; 7 | 8 | public interface PostPatcher extends Opcodes { 9 | PostPatcher NONE = new PostPatcher() { 10 | @Override 11 | public ClassVisitor patch(ClassVisitor classVisitor) { 12 | return classVisitor; 13 | } 14 | 15 | @Override 16 | public void post(Map remapJar) {} 17 | }; 18 | 19 | ClassVisitor patch(ClassVisitor classVisitor); 20 | 21 | void post(Map remapJar); 22 | 23 | default void appendManifest(StringBuilder stringBuilder) {} 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/fox2code/repacker/tests/Tests.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.tests; 2 | 3 | import com.fox2code.repacker.utils.Utils; 4 | import org.junit.Test; 5 | 6 | public class Tests { 7 | @Test 8 | public void testCountParmsOnSignature() { 9 | int r; 10 | if (1 != (r = Utils.countParms("(Ljava/lang/String;)Ljava/lang/String;"))) { 11 | throw new Error("testCountParmsOnSignature countParms result is not 1 (got "+r+")"); 12 | } 13 | } 14 | 15 | @Test 16 | public void testCountParmsOnGenericSignature() { 17 | int r; 18 | if (1 != (r = Utils.countParms("([TT;)Ljava/util/List;"))) { 19 | throw new Error("testCountParmsOnGenericSignature countParms result is not 1 (got "+r+")"); 20 | } 21 | } 22 | 23 | @Test 24 | public void testCountParmsCompare() { 25 | int r1, r2; 26 | if ((r1 = Utils.countParms("(Ljava/lang/String;)Ljava/lang/String;")) != 27 | (r2 = Utils.countParms("(Ljava/lang/String;)Ljava/lang/String;"))) { 28 | throw new Error("testCountParmsCompare failed ("+r1+" != "+r2+")"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repacker 2 | 1.14.4+ Minecraft open-source deobfuscator using official Mojang mappings 3 | 4 | (Work also with snapshots) 5 | 6 | ## Usage 7 | 8 | ### Gradle plugin 9 | 10 | See [UDK](https://github.com/Fox2Code/UDK) 11 | 12 | ### Command line 13 | 14 | `java -jar Repaker.jar (-server)` 15 | 16 | example: 17 | 18 | `java -jar Repacker.jar . 1.15.2` to repack MC 1.15.2 client 19 | 20 | and 21 | 22 | `java -jar Repacker.jar . 1.15.2-server` to repack MC 1.15.2 server 23 | 24 | also you can use 25 | 26 | `java -jar Repacker.jar . release` or `java -jar Repacker.jar . snapshot` 27 | to repack the latest snapshot or release of the game 28 | 29 | pre-release version names are VER-preX (Ex: 1.15-pre1) 30 | 31 | The id of `20w14∞` is actually `20w14infinite` 32 | 33 | ### Java 34 | 35 | example: 36 | 37 | ```Java 38 | import com.fox2code.repacker.Repacker; 39 | import com.fox2code.repacker.layout.FlatDirLayout; 40 | import java.io.File; 41 | 42 | String version = "1.15.2"; 43 | Repacker repacker = new Repacker(new FlatDirLayout(new File("cache"))); 44 | repacker.repackClient(version); 45 | File repackedClient = repacker.getClientRemappedFile(version); 46 | ``` 47 | 48 | ## Add as library 49 | 50 | WIP 51 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/layout/FlatDirLayout.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.layout; 2 | 3 | import java.io.File; 4 | 5 | public class FlatDirLayout implements DirLayout { 6 | protected File root; 7 | 8 | public FlatDirLayout(File root) { 9 | this.root = root; 10 | this.root.mkdirs(); 11 | } 12 | 13 | @Override 14 | public File getIndexCache() { 15 | return new File(root, "index-cache.json"); 16 | } 17 | 18 | @Override 19 | public File getVersionIndexFile(String version) { 20 | return new File(root, version+".json"); 21 | } 22 | 23 | @Override 24 | public File getMappingFile(String version, boolean client) { 25 | return new File(root, version+(client?"":"-server")+"-mappings.txt"); 26 | } 27 | 28 | @Override 29 | public File getMinecraftFile(String version, boolean client) { 30 | return new File(root, "minecraft-"+version+(client?"":"-server")+".jar"); 31 | } 32 | 33 | @Override 34 | public File getMinecraftRepackFile(String version, boolean client) { 35 | return new File(root, "minecraft-"+version+(client?"":"-server")+"-remapped.jar"); 36 | } 37 | 38 | @Override 39 | public void generateDirsFor(String version) {} 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/layout/MavenDirLayout.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.layout; 2 | 3 | import java.io.File; 4 | 5 | public class MavenDirLayout implements DirLayout { 6 | protected File root; 7 | 8 | public MavenDirLayout(File root) { 9 | this.root = root; 10 | this.root.mkdirs(); 11 | } 12 | 13 | @Override 14 | public File getIndexCache() { 15 | return new File(root, "index-cache.json"); 16 | } 17 | 18 | @Override 19 | public File getVersionIndexFile(String version) { 20 | return new File(root, "net/minecraft/minecraft/"+version+"/"+version+".json"); 21 | } 22 | 23 | @Override 24 | public File getMappingFile(String version, boolean client) { 25 | return new File(root, "net/minecraft/minecraft/"+version+"/"+(client?"client":"server")+"-mappings.txt"); 26 | } 27 | 28 | @Override 29 | public File getMinecraftFile(String version, boolean client) { 30 | return new File(root, "net/minecraft/minecraft/"+version+"/minecraft-"+version+(client?"":"-server")+".jar"); 31 | } 32 | 33 | @Override 34 | public File getMinecraftRepackFile(String version, boolean client) { 35 | return new File(root, "net/minecraft/minecraft/"+version+"/minecraft-"+version+(client?"":"-server")+"-remapped.jar"); 36 | } 37 | 38 | @Override 39 | public void generateDirsFor(String version) { 40 | new File(root, "net/minecraft/minecraft/"+version).mkdirs(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Fox2Code 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/patchers/PostPatcherStack.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.patchers; 2 | 3 | import org.objectweb.asm.ClassVisitor; 4 | 5 | import java.util.Iterator; 6 | import java.util.LinkedList; 7 | import java.util.Map; 8 | 9 | public class PostPatcherStack implements PostPatcher, Iterable { 10 | private final LinkedList linkedList = new LinkedList<>(); 11 | 12 | public PostPatcherStack() {} 13 | 14 | public PostPatcherStack(PostPatcher... patchers) { 15 | for (PostPatcher postPatcher:patchers) { 16 | this.addPatcher(postPatcher); 17 | } 18 | } 19 | 20 | public PostPatcherStack(Iterable patchers) { 21 | for (PostPatcher postPatcher:patchers) { 22 | this.addPatcher(postPatcher); 23 | } 24 | } 25 | 26 | @Override 27 | public ClassVisitor patch(ClassVisitor classVisitor) { 28 | for (PostPatcher postPatcher: linkedList) { 29 | classVisitor = postPatcher.patch(classVisitor); 30 | } 31 | return classVisitor; 32 | } 33 | 34 | @Override 35 | public void post(Map remapJar) { 36 | for (PostPatcher postPatcher: linkedList) { 37 | postPatcher.post(remapJar); 38 | } 39 | } 40 | 41 | @Override 42 | public void appendManifest(StringBuilder stringBuilder) { 43 | for (PostPatcher postPatcher: linkedList) { 44 | postPatcher.appendManifest(stringBuilder); 45 | } 46 | } 47 | 48 | @Override 49 | public Iterator iterator() { 50 | return linkedList.iterator(); 51 | } 52 | 53 | public void addPatcher(PostPatcher postPatcher) { 54 | if (postPatcher != null && postPatcher != PostPatcher.NONE) { 55 | linkedList.add(postPatcher); 56 | } 57 | } 58 | 59 | public static PostPatcher stack(PostPatcher... patchers) { 60 | if (patchers.length == 0) { 61 | return PostPatcher.NONE; 62 | } else if (patchers.length == 1) { 63 | return patchers[0]; 64 | } else { 65 | return new PostPatcherStack(patchers); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/patchers/ClientAnnotationPatcher.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.patchers; 2 | 3 | import com.fox2code.repacker.utils.Utils; 4 | import org.objectweb.asm.ClassVisitor; 5 | import org.objectweb.asm.FieldVisitor; 6 | import org.objectweb.asm.MethodVisitor; 7 | import org.objectweb.asm.Opcodes; 8 | 9 | import java.util.Map; 10 | 11 | public class ClientAnnotationPatcher implements PostPatcher, Opcodes { 12 | private final Mapping.ReverseMapping client; 13 | private final Mapping.ReverseMapping server; 14 | 15 | private static String repackerCst() { 16 | return "repacker"; // Hacky way to not get repacked by shadowJar 17 | } 18 | 19 | public ClientAnnotationPatcher(Mapping client,Mapping server) { 20 | this.client = client.getRawReverseMapping(); 21 | this.server = server.getRawReverseMapping(); 22 | } 23 | 24 | @Override 25 | public ClassVisitor patch(ClassVisitor classVisitor) { 26 | return new ClassPatcher(classVisitor); 27 | } 28 | 29 | @Override 30 | public void post(Map remapJar) { 31 | if (Utils.cjo != null) { 32 | remapJar.put("com/fox2code/"+repackerCst()+"/ClientJarOnly.class", Utils.cjo); 33 | } 34 | } 35 | 36 | @Override 37 | public void appendManifest(StringBuilder stringBuilder) {} 38 | 39 | public boolean isClientOnly(String className) { 40 | return client.cl.contains(className) && !server.cl.contains(className); 41 | } 42 | 43 | public boolean isClientOnly(String className, String name) { 44 | return client.f.contains(className+"#"+name) && !server.f.contains(className+"#"+name); 45 | } 46 | 47 | public boolean isClientOnly(String className, String name, String desc) { 48 | return client.m.contains(className+"#"+name) && !server.m.contains(className+"#"+name); 49 | } 50 | 51 | private class ClassPatcher extends ClassVisitor { 52 | boolean skip; 53 | String clName; 54 | 55 | public ClassPatcher(ClassVisitor classVisitor) { 56 | super(Utils.ASM_BUILD, classVisitor); 57 | this.skip = false; 58 | } 59 | 60 | @Override 61 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 62 | super.visit(version, access, name, signature, superName, interfaces); 63 | this.clName = name; 64 | if (isClientOnly(name)) { 65 | this.skip = true; 66 | super.visitAnnotation("Lcom/fox2code/"+repackerCst()+"/ClientJarOnly;", false).visitEnd(); 67 | } else if ((access & ACC_ANNOTATION) != 0) { 68 | this.skip = true; 69 | } 70 | } 71 | 72 | @Override 73 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 74 | FieldVisitor fieldVisitor = super.visitField(access, name, descriptor, signature, value); 75 | if (!skip) { 76 | if (isClientOnly(this.clName, name)) { 77 | fieldVisitor.visitAnnotation("Lcom/fox2code/"+repackerCst()+"/ClientJarOnly;", false).visitEnd(); 78 | } 79 | } 80 | return fieldVisitor; 81 | } 82 | 83 | @Override 84 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 85 | MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); 86 | if (!skip) { 87 | if (isClientOnly(this.clName, name, descriptor)) { 88 | methodVisitor.visitAnnotation("Lcom/fox2code/"+repackerCst()+"/ClientJarOnly;", false).visitEnd(); 89 | } 90 | } 91 | return methodVisitor; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/utils/ConsoleColors.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.utils; 2 | 3 | public class ConsoleColors { 4 | // Reset 5 | public static final String RESET = "\033[0m"; // Text Reset 6 | public static final String BOLD = "\033[1m"; // Bold effect 7 | 8 | // Regular Colors 9 | public static final String BLACK = "\033[0;30m"; // BLACK 10 | public static final String RED = "\033[0;31m"; // RED 11 | public static final String GREEN = "\033[0;32m"; // GREEN 12 | public static final String YELLOW = "\033[0;33m"; // YELLOW 13 | public static final String BLUE = "\033[0;34m"; // BLUE 14 | public static final String PURPLE = "\033[0;35m"; // PURPLE 15 | public static final String CYAN = "\033[0;36m"; // CYAN 16 | public static final String WHITE = "\033[0;37m"; // WHITE 17 | 18 | // Bold 19 | public static final String BLACK_BOLD = "\033[1;30m"; // BLACK 20 | public static final String RED_BOLD = "\033[1;31m"; // RED 21 | public static final String GREEN_BOLD = "\033[1;32m"; // GREEN 22 | public static final String YELLOW_BOLD = "\033[1;33m"; // YELLOW 23 | public static final String BLUE_BOLD = "\033[1;34m"; // BLUE 24 | public static final String PURPLE_BOLD = "\033[1;35m"; // PURPLE 25 | public static final String CYAN_BOLD = "\033[1;36m"; // CYAN 26 | public static final String WHITE_BOLD = "\033[1;37m"; // WHITE 27 | 28 | // Underline 29 | public static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACK 30 | public static final String RED_UNDERLINED = "\033[4;31m"; // RED 31 | public static final String GREEN_UNDERLINED = "\033[4;32m"; // GREEN 32 | public static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOW 33 | public static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUE 34 | public static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLE 35 | public static final String CYAN_UNDERLINED = "\033[4;36m"; // CYAN 36 | public static final String WHITE_UNDERLINED = "\033[4;37m"; // AROUF 37 | 38 | // Background 39 | public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK 40 | public static final String RED_BACKGROUND = "\033[41m"; // RED 41 | public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN 42 | public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW 43 | public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE 44 | public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE 45 | public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN 46 | public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE 47 | 48 | // High Intensity 49 | public static final String BLACK_BRIGHT = "\033[0;90m"; // BLACK 50 | public static final String RED_BRIGHT = "\033[0;91m"; // RED 51 | public static final String GREEN_BRIGHT = "\033[0;92m"; // GREEN 52 | public static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOW 53 | public static final String BLUE_BRIGHT = "\033[0;94m"; // BLUE 54 | public static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLE 55 | public static final String CYAN_BRIGHT = "\033[0;96m"; // CYAN 56 | public static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE 57 | 58 | // Bold High Intensity 59 | public static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACK 60 | public static final String RED_BOLD_BRIGHT = "\033[1;91m"; // RED 61 | public static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREEN 62 | public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOW 63 | public static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUE 64 | public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLE 65 | public static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYAN 66 | public static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE 67 | 68 | // High Intensity backgrounds 69 | public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK 70 | public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED 71 | public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN 72 | public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW 73 | public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE 74 | public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE 75 | public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN 76 | public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/Main.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker; 2 | 3 | import com.fox2code.repacker.layout.DirLayout; 4 | import com.fox2code.repacker.layout.FlatDirLayout; 5 | import com.fox2code.repacker.layout.MavenDirLayout; 6 | import com.fox2code.repacker.utils.ConsoleColors; 7 | import org.fusesource.jansi.AnsiConsole; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | public class Main { 13 | public static void main(String[] args) { 14 | try { // Note: Lib builds don't add jansi 15 | AnsiConsole.systemInstall(); 16 | } catch (Throwable ignored) {} 17 | if (args.length != 2 && (args.length != 3 || !args[0].startsWith("-") || args[0].length() == 1)) { 18 | String color = (args.length == 0 || 19 | (args.length == 1 && ("-h".equals(args[0]) || "--help".equals(args[0])))) 20 | ? ConsoleColors.BOLD : ConsoleColors.RED_BOLD; 21 | 22 | System.out.println(color + "usage: java -jar Repaker.jar --help"); 23 | System.out.println(color + "usage: java -jar Repaker.jar (-parms) (-server)"); 24 | System.out.println(color + " -f => force repackage of the .jar"); 25 | System.out.println(color + " -m => use maven dir layout"); 26 | System.out.println(color + " -c => clean temporary files when finished"); 27 | System.out.println(color + " -d => download only without repack" + ConsoleColors.RESET); 28 | return; 29 | } 30 | int d = args.length - 2; 31 | boolean force = false, maven = false, clean = false, download = false; 32 | if (d != 0) { 33 | String p = args[0]; 34 | int i = 1; 35 | while (i < p.length()) { 36 | char c = p.charAt(i); 37 | switch (c) { 38 | default: 39 | System.err.println(ConsoleColors.RED_BOLD + 40 | "Unknown argument: -" + c + ConsoleColors.RESET); 41 | System.exit(-2); 42 | return; 43 | case 'f': 44 | force = true; 45 | break; 46 | case 'm': 47 | maven = true; 48 | break; 49 | case 'c': 50 | clean = true; 51 | break; 52 | case 'd': 53 | download = true; 54 | break; 55 | } 56 | i++; 57 | } 58 | } 59 | File file = new File(args[d]); 60 | if (!file.exists()) { 61 | System.out.println(ConsoleColors.RED_BOLD + "Cache dir doesn't exists!" + ConsoleColors.RESET); 62 | return; 63 | } 64 | boolean server = args[d+1].endsWith("-server"); 65 | if (server) { 66 | args[d+1] = args[d+1].substring(0, args[d+1].length()-7); 67 | } 68 | try { 69 | Repacker repacker = new Repacker(maven ? 70 | new MavenDirLayout(file) : new FlatDirLayout(file)); 71 | args[d+1] = repacker.realVersion(args[d+1]); 72 | DirLayout dirLayout = repacker.getDirLayout(); 73 | if (server) { 74 | if (force) { 75 | if (download) { 76 | repacker.getServerFile(args[d+1]).delete(); 77 | } else { 78 | repacker.getServerRemappedFile(args[d+1]).delete(); 79 | } 80 | } 81 | if (download) { 82 | repacker.downloadServer(args[d+1]); 83 | } else { 84 | repacker.repackServer(args[d + 1]); 85 | } 86 | if (clean) { 87 | System.out.println(ConsoleColors.YELLOW_BRIGHT +"Cleaning files..." + ConsoleColors.RESET); 88 | if (!download) { 89 | dirLayout.getMinecraftFile(args[d + 1], false).delete(); 90 | } 91 | } 92 | } else { 93 | if (force) { 94 | if (download) { 95 | repacker.getClientFile(args[d+1]).delete(); 96 | } else { 97 | repacker.getClientRemappedFile(args[d+1]).delete(); 98 | } 99 | } 100 | if (download) { 101 | repacker.downloadClient(args[d+1]); 102 | } else { 103 | repacker.repackClient(args[d + 1]); 104 | } 105 | if (clean) { 106 | System.out.println(ConsoleColors.YELLOW_BRIGHT + "Cleaning files..." + ConsoleColors.RESET); 107 | if (!download) { 108 | dirLayout.getMinecraftFile(args[d + 1], true).delete(); 109 | dirLayout.getMappingFile(args[d + 1], true).delete(); 110 | } 111 | } 112 | } 113 | if (clean) { 114 | if (!download) { 115 | dirLayout.getMappingFile(args[d + 1], false).delete(); 116 | } 117 | dirLayout.getVersionIndexFile(args[d+1]).delete(); 118 | dirLayout.getIndexCache().delete(); 119 | } 120 | } catch (IOException e) { 121 | e.printStackTrace(); 122 | System.exit(-1); 123 | } 124 | System.out.println(ConsoleColors.GREEN_BRIGHT + "Finished!" + ConsoleColors.RESET); 125 | System.exit(0); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/patchers/Mapping.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.patchers; 2 | 3 | import com.fox2code.repacker.utils.Utils; 4 | import org.objectweb.asm.commons.Remapper; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.file.Files; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | public class Mapping extends Remapper { 15 | private final HashMap map; 16 | private final HashMap methods; 17 | private final HashMap fields; 18 | ReverseMapping cachedReverseMapping = null; 19 | 20 | public Mapping(File file) throws IOException { 21 | this(Files.newInputStream(file.toPath())); 22 | } 23 | 24 | public Mapping(InputStream is) throws IOException { 25 | this(Utils.readAll(is)); 26 | } 27 | 28 | public Mapping(String raw) { 29 | map = new HashMap<>(); 30 | methods = new HashMap<>(); 31 | fields = new HashMap<>(); 32 | HashMap reverseMap = new HashMap<>(); 33 | String[] list = raw.split("\\n"); 34 | for (String line:list) { 35 | if (line.startsWith("#") || line.startsWith(" ") || line.isEmpty()) { 36 | continue; 37 | } 38 | while (line.endsWith(" ") || line.endsWith("\r") || line.endsWith(":")) { 39 | line = line.substring(0, line.length() - 1); 40 | } 41 | int index = line.indexOf(" -> "); 42 | String substring = line.substring(index + 4); 43 | reverseMap.put(line.substring(0, index), substring); 44 | map.put(substring.replace('.','/'), line.substring(0, index).replace('.','/')); 45 | } 46 | String context = ""; 47 | for (String line:list) { 48 | if (line.startsWith("#") || line.isEmpty()) { 49 | continue; 50 | } 51 | while (line.endsWith(" ") || line.endsWith("\r") || line.endsWith(":")) { 52 | line = line.substring(0, line.length() - 1); 53 | } 54 | if (!line.startsWith(" ")) { 55 | int index = line.indexOf(" -> "); 56 | context = line.substring(index + 4).replace('.','/'); 57 | continue; 58 | } 59 | line = line.substring(4); 60 | int index1 = line.indexOf(" "); 61 | int index2 = line.indexOf(" -> "); 62 | String first = line.substring(index1+1, index2); 63 | int index3 = first.indexOf('('); 64 | if (index3 == -1) { 65 | fields.put(context+":"+line.substring(index2+4), first); 66 | continue; 67 | } 68 | String[] args = first.substring(index3+1, first.length()-1).split(","); 69 | String output = line.substring(0, index1); 70 | output = output.substring(output.lastIndexOf(':')+1); 71 | StringBuilder desc = new StringBuilder(); 72 | desc.append(context).append('.').append(line.substring(index2+4)).append('('); 73 | for (String arg:args) if (!arg.isEmpty()) { 74 | desc.append(descify(arg, reverseMap)); 75 | } 76 | desc.append(')').append(descify(output, reverseMap)); 77 | methods.put(desc.toString(), first.substring(0, index3)); 78 | } 79 | } 80 | 81 | @Override 82 | public String map(String internalName) { 83 | return map.getOrDefault(internalName, internalName); 84 | } 85 | 86 | @Override 87 | public String mapType(String internalName) { 88 | if (internalName != null && internalName.startsWith("L") && internalName.endsWith(";")) { 89 | return "L"+this.map(internalName.substring(1, internalName.length()-1))+";"; 90 | } else { 91 | return super.mapType(internalName); 92 | } 93 | } 94 | 95 | @Override 96 | public String mapFieldName(String owner, String name, String descriptor) { 97 | return fields.getOrDefault(owner+":"+name, name); 98 | } 99 | 100 | @Override 101 | public String mapMethodName(String owner, String name, String descriptor) { 102 | return methods.getOrDefault(owner+"."+name+descriptor, name); 103 | } 104 | 105 | private static String descify(String str, HashMap map) { 106 | if (str.endsWith("[]")) { 107 | return "["+descify(str.substring(0, str.length()-2), map); 108 | } 109 | switch (str) { 110 | default: 111 | str = map.getOrDefault(str, str); 112 | return "L"+str.replace('.','/')+";"; 113 | case "int": 114 | return "I"; 115 | case "boolean": 116 | return "Z"; 117 | case "short": 118 | return "S"; 119 | case "long": 120 | return "J"; 121 | case "byte": 122 | return "B"; 123 | case "float": 124 | return "F"; 125 | case "void": 126 | return "V"; 127 | case "char": 128 | return "C"; 129 | case "double": 130 | return "D"; 131 | } 132 | } 133 | 134 | public void remap(File in, File out) throws IOException { 135 | Utils.remap(in, out, this); 136 | } 137 | 138 | public void remap(File in, File out, PostPatcher postPatcher) throws IOException { 139 | Utils.remap(in, out, this, postPatcher); 140 | } 141 | 142 | @Override 143 | public String toString() { 144 | return "{classes:"+map+",methods:"+methods+",fields:"+fields+"}"; 145 | } 146 | 147 | @Override 148 | public int hashCode() { 149 | return map.hashCode() ^ methods.hashCode() ^ fields.hashCode(); 150 | } 151 | 152 | @Override 153 | public boolean equals(Object obj) { 154 | return obj instanceof Mapping && ((Mapping) obj).map.equals(this.map) && ((Mapping) obj).methods.equals(this.methods) && ((Mapping) obj).fields.equals(this.fields); 155 | } 156 | 157 | public HashMap getFields() { 158 | return fields; 159 | } 160 | 161 | public HashMap getMap() { 162 | return map; 163 | } 164 | 165 | public HashMap getMethods() { 166 | return methods; 167 | } 168 | 169 | ReverseMapping getRawReverseMapping() { 170 | if (cachedReverseMapping == null) { 171 | cachedReverseMapping = new ReverseMapping(); 172 | cachedReverseMapping.cl.addAll(this.map.values()); 173 | this.fields.forEach((k, v) -> { 174 | cachedReverseMapping.f.add(k.substring(0, k.indexOf(':'))+"#"+v); 175 | }); 176 | this.methods.forEach((k, v) -> { 177 | cachedReverseMapping.m.add(k.substring(0, k.indexOf('.'))+"#"+v); 178 | }); 179 | } 180 | return cachedReverseMapping; 181 | } 182 | 183 | static class ReverseMapping { 184 | private ReverseMapping() {} 185 | 186 | public Set cl = new HashSet<>(); 187 | public Set m = new HashSet<>(); 188 | public Set f = new HashSet<>(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/patchers/BytecodeFixer.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.patchers; 2 | 3 | import com.fox2code.repacker.utils.ConsoleColors; 4 | import com.fox2code.repacker.utils.Utils; 5 | import org.objectweb.asm.*; 6 | 7 | import java.util.HashMap; 8 | 9 | /** 10 | * Fix and Improve remapped Bytecode 11 | * Mainly help debug 12 | */ 13 | public class BytecodeFixer extends ClassVisitor implements Opcodes { 14 | public BytecodeFixer(ClassVisitor classVisitor) { 15 | super(Utils.ASM_BUILD, classVisitor); 16 | } 17 | 18 | private String sourceName; 19 | private String self; 20 | 21 | @Override 22 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 23 | int i, i$; 24 | this.sourceName = name.substring((i = name.lastIndexOf('/'))+1, (((i$ = name.indexOf('$')) != -1) && i < i$ )? i$ : name.length())+".java"; 25 | this.self = name; 26 | super.visit(version, access, name, signature, superName, interfaces); 27 | } 28 | 29 | @Override 30 | public void visitSource(String source, String debug) { 31 | super.visitSource((source == null || source.equals("SourceFile")) ? this.sourceName : source, debug); 32 | } 33 | 34 | @Override 35 | public MethodVisitor visitMethod(int access,final String mName,final String mDescriptor, String signature, String[] exceptions) { 36 | final int parms = Utils.countParms(mDescriptor); 37 | final boolean isStatic = (access & ACC_STATIC) != 0; 38 | final int limit = Utils.countIndexParms(mDescriptor) + ( isStatic ? 0 : 1); 39 | // Signature may not match with description due to obfuscation 40 | if (signature == null || Utils.countParms(signature) != parms) signature = null; 41 | return new MethodVisitor(Utils.ASM_BUILD, super.visitMethod( 42 | access, mName, mDescriptor, signature, exceptions)) { 43 | int i; 44 | final HashMap parmsNames = new HashMap<>(); 45 | 46 | @Override 47 | public void visitVarInsn(int opcode, int var) { 48 | super.visitVarInsn(opcode, var); 49 | if (opcode >= 21 && opcode <= 25) { 50 | i = var; 51 | } else { 52 | i = 0; 53 | } 54 | } 55 | 56 | @Override 57 | public void visitInsn(int opcode) { 58 | super.visitInsn(opcode); 59 | i = 0; 60 | } 61 | 62 | @Override 63 | public void visitLdcInsn(Object value) { 64 | super.visitLdcInsn(value); 65 | i = 0; 66 | } 67 | 68 | @Override 69 | public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { 70 | super.visitFieldInsn(opcode, owner, name, descriptor); 71 | if ((opcode == PUTFIELD || opcode == PUTSTATIC) && i != 0 && limit > i && owner.equals(self)) { 72 | parmsNames.putIfAbsent(i, name); 73 | } 74 | i = 0; 75 | } 76 | 77 | @Override 78 | public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { 79 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 80 | if ((opcode == INVOKEVIRTUAL || opcode == INVOKESPECIAL) && i != 0 && limit > i && (opcode == INVOKESPECIAL || owner.equals(self))) { 81 | if (name.startsWith("set") && name.length() > 3 && Utils.countParms(descriptor) == 1) { 82 | parmsNames.putIfAbsent(i, name.substring(3, 4).toLowerCase()+name.substring(4)); 83 | } 84 | } 85 | i = 0; 86 | } 87 | 88 | private String nameFromDesc(String descriptor) { 89 | String name = descriptor; 90 | boolean plural = false; 91 | while (name.startsWith("[")) { 92 | name = name.substring(1); 93 | plural = true; 94 | } 95 | if (name.length() == 1) { 96 | switch (name) { 97 | case "J": 98 | return "longs"; 99 | case "D": 100 | return "doubles"; 101 | case "Z": 102 | return "booleans"; 103 | case "B": 104 | return "bytes"; 105 | case "I": 106 | return "ints"; 107 | case "F": 108 | return "floats"; 109 | case "C": 110 | return "chars"; 111 | case "S": 112 | return "shorts"; 113 | } 114 | return ""; 115 | } 116 | name = name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf('/') + 2).toLowerCase() 117 | + name.substring(name.lastIndexOf('/') + 2, name.length() - 1); 118 | if (plural) { 119 | if (name.charAt(name.length()-1) == 's') { 120 | name = name + "es"; 121 | } else { 122 | name = name + "s"; 123 | } 124 | } 125 | return name; 126 | } 127 | 128 | @Override 129 | public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { 130 | if (index == 0 && !isStatic) { 131 | name = "this"; 132 | } else if ((name.length() <= 1 && name.charAt(0) > 128) || 133 | name.startsWith("$$")) { 134 | if (parms == 1 && index == (isStatic ? 0 : 1)) { 135 | if (descriptor.length() == 1) { 136 | switch (descriptor.charAt(0)) { 137 | default: 138 | name = descriptor.toLowerCase(); 139 | break; 140 | case 'Z': 141 | name = "b"; 142 | break; 143 | case 'J': 144 | name = "l"; 145 | break; 146 | } 147 | } else { 148 | name = nameFromDesc(descriptor); 149 | } 150 | if (mName.startsWith("getBy") && mName.length() > 5) { 151 | name = mName.substring(5,6).toLowerCase()+mName.substring(6); 152 | } else if (mName.startsWith("from") && mName.length() > 4) { 153 | name = mName.substring(4,5).toLowerCase()+mName.substring(5); 154 | } else if (mName.startsWith("set") && mName.length() > 3 && Character.isUpperCase(mName.charAt(3))) { 155 | name = mName.substring(3,4).toLowerCase()+mName.substring(4); 156 | } else if (mName.startsWith("by") && mName.length() > 2 && Character.isUpperCase(mName.charAt(2))) { 157 | name = mName.substring(2,3).toLowerCase()+mName.substring(3); 158 | } else if (isStatic && descriptor.charAt(0) == '[' && mName.equals("main")) { 159 | name = "args"; 160 | } else { 161 | name = parmsNames.getOrDefault(index, name); 162 | } 163 | } else { 164 | name = (descriptor.charAt(0) == '[' ? "vars" : "var")+index; 165 | if (index <= limit && (descriptor.charAt(0) == 'L' || descriptor.charAt(0) == '[') && mDescriptor.contains(descriptor) 166 | && ! mDescriptor.substring(mDescriptor.indexOf(descriptor)+descriptor.length()).contains(descriptor)) { 167 | name = nameFromDesc(descriptor); 168 | } 169 | int i2 = isStatic ? i + 1 : i; 170 | if (parms == 3 && i2 > 0 && i2 <= 5 && mDescriptor.startsWith("(DDD)")) { 171 | if (mName.toLowerCase().contains("color") || 172 | mName.toLowerCase().contains("rgb")) { 173 | switch (i2) { 174 | case 1: 175 | name = "r"; 176 | break; 177 | case 3: 178 | name = "g"; 179 | break; 180 | case 5: 181 | name = "b"; 182 | break; 183 | } 184 | } else { 185 | switch (i2) { 186 | case 1: 187 | name = "x"; 188 | break; 189 | case 3: 190 | name = "y"; 191 | break; 192 | case 5: 193 | name = "z"; 194 | break; 195 | } 196 | } 197 | } 198 | name = parmsNames.getOrDefault(index, name); 199 | } 200 | } 201 | // Fix invalid var names just in case 202 | if (name == null || name.isEmpty() || (name.length() == 1 && name.charAt(0) > 128) 203 | || name.charAt(0) == '[' || name.startsWith("$$")) { 204 | // This piece of code should NEVER be called 205 | System.out.println(ConsoleColors.YELLOW+ "Warning:" + ConsoleColors.RESET + 206 | " Invalid var name: \"" + name + "\" for \"" + descriptor+"\""); 207 | name = "var" + index; 208 | } 209 | super.visitLocalVariable(name, descriptor, signature, start, end, index); 210 | } 211 | }; 212 | } 213 | 214 | @Override 215 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 216 | return super.visitField(access, name, descriptor, signature, value); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/rebuild/ClassDataProvider.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.rebuild; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassVisitor; 5 | import org.objectweb.asm.ClassWriter; 6 | 7 | import java.lang.reflect.Modifier; 8 | import java.lang.reflect.Type; 9 | import java.security.SecureClassLoader; 10 | import java.util.*; 11 | 12 | import static org.objectweb.asm.Opcodes.ACC_PUBLIC; 13 | import static org.objectweb.asm.Opcodes.ASM6; 14 | 15 | public class ClassDataProvider { 16 | public static boolean debugClassResolution = "true".equalsIgnoreCase( 17 | System.getProperty("repacker.debug.cdp", System.getProperty("repacker.debug"))); 18 | private static final ClassLoader BOOTSTRAP_CLASS_LOADER = new SecureClassLoader(null) {}; 19 | 20 | private static final ClData object; 21 | private static final ClData objectArray; 22 | 23 | static { 24 | object = new ObjectCLData(); 25 | objectArray = new ObjectCLDataArray(); 26 | } 27 | 28 | private final HashMap clDataHashMap; 29 | private final ClassLoader classLoader; 30 | 31 | public ClassDataProvider(ClassLoader classLoader) { 32 | this.clDataHashMap = new HashMap<>(); 33 | this.clDataHashMap.put("java/lang/Object", object); 34 | this.clDataHashMap.put("[java/lang/Object", objectArray); 35 | this.classLoader = classLoader == null ? 36 | ClassLoader.getSystemClassLoader() : classLoader; 37 | } 38 | 39 | public static class ClData implements ClassData { 40 | 41 | final String name; 42 | String superClass; 43 | int access; 44 | 45 | private ClData(String name) { 46 | this.name = name; 47 | this.access = ACC_PUBLIC; 48 | } 49 | 50 | @Override 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | @Override 56 | public boolean isInterface() { 57 | return Modifier.isInterface(this.access); 58 | } 59 | 60 | @Override 61 | public boolean isFinal() { 62 | return Modifier.isFinal(this.access); 63 | } 64 | 65 | @Override 66 | public boolean isPublic() { 67 | return Modifier.isPublic(this.access); 68 | } 69 | 70 | @Override 71 | public boolean isCustom() { 72 | return false; 73 | } 74 | 75 | @Override 76 | public ClData getSuperclass() { 77 | return null; 78 | } 79 | 80 | @Override 81 | public ClassData[] getInterfaces() { 82 | return new ClassData[0]; 83 | } 84 | 85 | @Override 86 | public boolean isAssignableFrom(ClassData clData) { 87 | while (clData != null) { 88 | if (clData==this) return true; 89 | clData = clData.getSuperclass(); 90 | } 91 | return false; 92 | } 93 | 94 | } 95 | 96 | private static final class ObjectCLData extends ClData { 97 | private ObjectCLData() { 98 | super("java/lang/Object"); 99 | } 100 | 101 | @Override 102 | public boolean isAssignableFrom(ClassData clData) { 103 | return clData == this; 104 | } 105 | } 106 | 107 | private static final class ObjectCLDataArray extends ClData { 108 | private ObjectCLDataArray() { 109 | super("[java/lang/Object"); 110 | } 111 | 112 | @Override 113 | public boolean isAssignableFrom(ClassData clData) { 114 | return clData == this || clData == object; 115 | } 116 | 117 | @Override 118 | public ClData getSuperclass() { 119 | return object; 120 | } 121 | } 122 | 123 | class ClData2 extends ClData { 124 | List interfaces; 125 | List guessedSup; 126 | boolean custom = false; 127 | 128 | private ClData2(String name) { 129 | super(name); 130 | } 131 | 132 | @Override 133 | public ClData getSuperclass() { 134 | if (this.superClass==null) return null; 135 | return getClassData(this.superClass); 136 | } 137 | 138 | @Override 139 | public ClassData[] getInterfaces() { 140 | if (interfaces == null) { 141 | return new ClassData[0]; 142 | } 143 | ClassData[] classData = new ClassData[interfaces.size()]; 144 | int i = 0; 145 | for (String inName:interfaces) { 146 | classData[i] = getClassData(inName); 147 | i++; 148 | } 149 | return classData; 150 | } 151 | 152 | @Override 153 | public boolean isAssignableFrom(ClassData clData) { 154 | if (clData == null) return false; 155 | if (clData instanceof ClData2) { 156 | if (((ClData2) clData).interfaces != null) { 157 | for (String cl : ((ClData2) clData).interfaces) { 158 | if (this.isAssignableFrom(getClassData(cl))) { 159 | return true; 160 | } 161 | } 162 | } 163 | if (((ClData2) clData).guessedSup != null) { 164 | for (String cl : ((ClData2) clData).guessedSup) { 165 | if (this.isAssignableFrom(getClassData(cl))) { 166 | return true; 167 | } 168 | } 169 | } 170 | } 171 | do { 172 | if (clData == this) return true; 173 | clData = clData.getSuperclass(); 174 | } while (clData != null); 175 | return false; 176 | } 177 | 178 | @Override 179 | public boolean isCustom() { 180 | return custom; 181 | } 182 | } 183 | 184 | class ClData2Array extends ClData { 185 | private final ClData clData; 186 | 187 | private ClData2Array(ClData clData) { 188 | super("["+clData.getName()); 189 | this.clData = clData; 190 | this.access = clData.access; 191 | } 192 | 193 | @Override 194 | public ClData getSuperclass() { 195 | return ClassDataProvider.this.getClassData("["+clData.superClass); 196 | } 197 | 198 | @Override 199 | public boolean isCustom() { 200 | return clData.isCustom(); 201 | } 202 | } 203 | 204 | public ClData getClassData(String clName) { 205 | if (clName.endsWith(";")) { 206 | throw new IllegalArgumentException("Can't put desc as class Data -> "+clName); 207 | } 208 | clName = clName.replace('.','/'); 209 | ClData clData = clDataHashMap.get(clName); 210 | if (clData!=null) return clData; 211 | if (clName.startsWith("[")) { 212 | clDataHashMap.put(clName, clData = new ClData2Array(getClassData(clName.substring(1)))); 213 | return clData; 214 | } 215 | clData = new ClData2(clName); 216 | final ClData2 tClData = (ClData2) clData; 217 | try { 218 | ClassReader classReader = new ClassReader(Objects.requireNonNull(this.classLoader.getResourceAsStream(clName + ".class"))); 219 | classReader.accept(new ClassVisitor(ASM6) { 220 | @Override 221 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 222 | tClData.access = access; 223 | tClData.superClass = superName; 224 | if (interfaces != null && interfaces.length != 0) { 225 | tClData.interfaces = Arrays.asList(interfaces); 226 | } 227 | } 228 | }, ClassReader.SKIP_CODE); 229 | } catch (Exception e) { 230 | try { // Try to use the boot class loader as a fallback (Help fix issues on Java9+) 231 | Class cl = Class.forName(clName.replace('/','.'), false, BOOTSTRAP_CLASS_LOADER); 232 | tClData.access = cl.getModifiers(); 233 | tClData.superClass = cl.getGenericSuperclass().getTypeName().replace('.','/'); 234 | Type[] classes = cl.getGenericInterfaces(); 235 | if (classes.length != 0) { 236 | String[] interfaces = new String[classes.length]; 237 | for (int i = 0; i < interfaces.length;i++) { 238 | interfaces[i] = classes[i].getTypeName().replace('.','/'); 239 | } 240 | tClData.interfaces = Arrays.asList(interfaces); 241 | } 242 | } catch (Exception e2) { 243 | if (debugClassResolution) { 244 | System.out.println("DEBUG: Failed to resolve -> " + clName); 245 | } 246 | clData.superClass = "java/lang/Object"; 247 | } 248 | } 249 | clDataHashMap.put(clName,clData); 250 | return clData; 251 | } 252 | 253 | public ClassWriter newClassWriter() { 254 | return new ClassWriter(ClassWriter.COMPUTE_FRAMES) { 255 | @Override 256 | protected String getCommonSuperClass(String type1, String type2) { 257 | if (type1.equals(type2)) return type1; 258 | if (type1.equals("java/lang/Object") || type2.equals("java/lang/Object")) return "java/lang/Object"; 259 | try { 260 | ClData c, d; 261 | try { 262 | c = getClassData(type1); 263 | d = getClassData(type2); 264 | } catch (Exception e) { 265 | throw new RuntimeException(e.toString()); 266 | } 267 | if (c.isAssignableFrom(d)) { 268 | return type1; 269 | } 270 | if (d.isAssignableFrom(c)) { 271 | return type2; 272 | } 273 | if (c.isInterface() || d.isInterface()) { 274 | return "java/lang/Object"; 275 | } else { 276 | do { 277 | c = c.getSuperclass(); 278 | } while (!c.isAssignableFrom(d)); 279 | return c.getName().replace('.', '/'); 280 | } 281 | } catch (Exception e) { 282 | return "java/lang/Object"; 283 | } 284 | } 285 | }; 286 | } 287 | 288 | public void addClasses(Map classes) { 289 | for (Map.Entry entry:classes.entrySet()) if (entry.getKey().endsWith(".class")) { 290 | String name = entry.getKey().substring(0,entry.getKey().length()-6); 291 | ClData2 clData = new ClData2(name); 292 | try { 293 | ClassReader classReader = new ClassReader(entry.getValue()); 294 | classReader.accept(new ClassVisitor(ASM6) { 295 | @Override 296 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 297 | clData.access = access; 298 | clData.superClass = superName; 299 | clData.custom = true; 300 | if (interfaces != null && interfaces.length != 0) { 301 | clData.interfaces = Arrays.asList(interfaces); 302 | } 303 | } 304 | }, ClassReader.SKIP_CODE); 305 | } catch (Exception e) { 306 | if (debugClassResolution) { 307 | System.out.println("DEBUG: Invalid input class -> " + name); 308 | } 309 | clData.superClass = "java/lang/Object"; 310 | } 311 | clDataHashMap.put(name, clData); 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/Repacker.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker; 2 | 3 | import com.fox2code.repacker.layout.DirLayout; 4 | import com.fox2code.repacker.layout.MavenDirLayout; 5 | import com.fox2code.repacker.patchers.ClientAnnotationPatcher; 6 | import com.fox2code.repacker.patchers.Mapping; 7 | import com.fox2code.repacker.patchers.PostPatcher; 8 | import com.fox2code.repacker.utils.ConsoleColors; 9 | import com.fox2code.repacker.utils.RepackException; 10 | import com.fox2code.repacker.utils.Utils; 11 | import com.google.gson.JsonObject; 12 | import com.google.gson.JsonParser; 13 | import org.objectweb.asm.ClassVisitor; 14 | 15 | import java.io.*; 16 | import java.nio.charset.StandardCharsets; 17 | import java.nio.file.Files; 18 | import java.util.*; 19 | 20 | public class Repacker { 21 | private final LinkedHashMap cache; 22 | private final PrintStream out; 23 | private final DirLayout dirLayout; 24 | private String lastRelease; 25 | private String lastSnapshot; 26 | 27 | @Deprecated 28 | public Repacker(File cacheDir) { 29 | this(cacheDir, System.out); 30 | } 31 | 32 | @Deprecated 33 | public Repacker(File cacheDir,PrintStream out) { 34 | this(new MavenDirLayout(cacheDir), out); 35 | } 36 | 37 | public Repacker(DirLayout dirLayout) { 38 | this(dirLayout, System.out); 39 | } 40 | 41 | public Repacker(DirLayout dirLayout, PrintStream out) { 42 | this.dirLayout = dirLayout; 43 | this.cache = new LinkedHashMap<>(); 44 | this.out = out; 45 | } 46 | 47 | public void repackClient(String version) throws IOException { 48 | this.repackClient(version, null); 49 | } 50 | 51 | public void repackClient(String version, PostPatcher postPatcher) throws IOException { 52 | File versionIndex = dirLayout.getVersionIndexFile(version); 53 | File versionJar = dirLayout.getMinecraftFile(version, true); 54 | File versionMappings = dirLayout.getMappingFile(version, true); 55 | File versionMappingsSrv = dirLayout.getMappingFile(version, false); 56 | File versionJarRemap = dirLayout.getMinecraftRepackFile(version, true); 57 | if (!versionIndex.exists() || !versionJar.exists() || !versionMappings.exists() || !versionJarRemap.exists()) { 58 | JsonObject jsonObject = getVersionManifest(version); 59 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 60 | if (!downloads.has("client_mappings")) { 61 | throw new RepackException("Missing Obfuscation mapping in the current version!"); 62 | } 63 | if (!versionJar.exists()) { 64 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading client jar..." + ConsoleColors.RESET); 65 | Utils.download(downloads.getAsJsonObject("client").get("url").getAsString(), Files.newOutputStream(versionJar.toPath())); 66 | } 67 | Mapping mapping = getMappings(versionMappings, downloads.getAsJsonObject("client_mappings").get("url").getAsString(), "client"); 68 | Mapping mappingSrv = getMappings(versionMappingsSrv, downloads.getAsJsonObject("server_mappings").get("url").getAsString(), "server"); 69 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Parsing mapping..." + ConsoleColors.RESET); 70 | ClientAnnotationPatcher clientAnnotationPatcher = new ClientAnnotationPatcher(mapping, mappingSrv); 71 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Remapping client jar..." + ConsoleColors.RESET); 72 | mapping.remap(versionJar, versionJarRemap, new LogPatcher(clientAnnotationPatcher, postPatcher, "client")); 73 | } 74 | } 75 | 76 | public void downloadClient(String version) throws IOException { 77 | File versionJar = dirLayout.getMinecraftFile(version, true); 78 | if (!versionJar.exists()) { 79 | JsonObject jsonObject = getVersionManifest(version); 80 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 81 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading client jar..." + ConsoleColors.RESET); 82 | Utils.download(downloads.getAsJsonObject("client").get("url").getAsString(), Files.newOutputStream(versionJar.toPath())); 83 | } 84 | } 85 | 86 | public void repackServer(String version) throws IOException { 87 | this.repackServer(version, null); 88 | } 89 | 90 | public void repackServer(String version,PostPatcher postPatcher) throws IOException { 91 | File versionIndex = dirLayout.getVersionIndexFile(version); 92 | File versionJar = dirLayout.getMinecraftFile(version, false); 93 | File versionMappings = dirLayout.getMappingFile(version, false); 94 | File versionJarRemap = dirLayout.getMinecraftRepackFile(version, false); 95 | if (!versionIndex.exists() || !versionJar.exists() || !versionMappings.exists() || !versionJarRemap.exists()) { 96 | JsonObject jsonObject = getVersionManifest(version); 97 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 98 | if (!downloads.has("client_mappings")) { 99 | throw new RepackException("Missing Obfuscation mapping in the current version!"); 100 | } 101 | if (!versionJar.exists()) { 102 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading server jar..." + ConsoleColors.RESET); 103 | Utils.download(downloads.getAsJsonObject("server").get("url").getAsString(), Files.newOutputStream(versionJar.toPath())); 104 | } 105 | Mapping mapping = getMappings(versionMappings, downloads.getAsJsonObject("server_mappings").get("url").getAsString(), "server"); 106 | 107 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Remapping server jar..." + ConsoleColors.RESET); 108 | mapping.remap(versionJar, versionJarRemap, new LogPatcher(postPatcher, "server")); 109 | } 110 | } 111 | 112 | public void downloadServer(String version) throws IOException { 113 | File versionJar = dirLayout.getMinecraftFile(version, false); 114 | if (!versionJar.exists()) { 115 | JsonObject jsonObject = getVersionManifest(version); 116 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 117 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading client jar..." + ConsoleColors.RESET); 118 | Utils.download(downloads.getAsJsonObject("server").get("url").getAsString(), Files.newOutputStream(versionJar.toPath())); 119 | } 120 | } 121 | 122 | public JsonObject getVersionManifest(String version) throws IOException { 123 | return this.getVersionManifest0(version, false); 124 | } 125 | 126 | private JsonObject getVersionManifest0(String version,boolean check) throws IOException { 127 | if (version == null) { 128 | throw new NullPointerException(ConsoleColors.RED_BOLD + "Version cannot be null!" + ConsoleColors.RESET); 129 | } 130 | String realVersion = version; 131 | if (check) { 132 | switch (version) { 133 | case "release": 134 | realVersion = lastRelease; 135 | break; 136 | case "snapshot": 137 | realVersion = lastSnapshot; 138 | break; 139 | } 140 | } 141 | File versionIndex = realVersion != null ? 142 | dirLayout.getVersionIndexFile(realVersion) : null; 143 | if (versionIndex != null && versionIndex.exists()) { 144 | try { 145 | return (JsonObject) JsonParser.parseString(Utils.readAll(Files.newInputStream(versionIndex.toPath()))); 146 | } catch (Exception e) { 147 | versionIndex.delete(); 148 | } 149 | } 150 | 151 | File verCache = dirLayout.getIndexCache(); 152 | if (cache.isEmpty() && verCache.exists() && realVersion != null) { 153 | try { 154 | JsonObject jsonObject = (JsonObject) JsonParser.parseString(Utils.readAll(Files.newInputStream(versionIndex.toPath()))); 155 | jsonObject.getAsJsonArray().forEach(jsonElement -> { 156 | JsonObject object = jsonElement.getAsJsonObject(); 157 | cache.put(object.get("id").getAsString(), object.get("url").getAsString()); 158 | }); 159 | } catch (Exception e) { 160 | verCache.delete(); 161 | } 162 | } 163 | String verURL = cache.get(realVersion); 164 | if (verURL == null || realVersion == null) { 165 | String json = Utils.get("https://launchermeta.mojang.com/mc/game/version_manifest.json"); 166 | Files.write(verCache.toPath(), json.getBytes(StandardCharsets.UTF_8)); 167 | JsonObject jsonObject = (JsonObject) JsonParser.parseString(json); 168 | jsonObject.getAsJsonArray("versions").forEach(jsonElement -> { 169 | JsonObject object = jsonElement.getAsJsonObject(); 170 | cache.put(object.get("id").getAsString(), object.get("url").getAsString()); 171 | }); 172 | jsonObject = jsonObject.getAsJsonObject("latest"); 173 | lastRelease = jsonObject.get("release").getAsString(); 174 | lastSnapshot = jsonObject.get("snapshot").getAsString(); 175 | if (realVersion == null) { 176 | switch (version) { 177 | case "release": 178 | realVersion = lastRelease; 179 | break; 180 | case "snapshot": 181 | realVersion = lastSnapshot; 182 | break; 183 | } 184 | } 185 | verURL = cache.get(realVersion); 186 | if (verURL == null) { 187 | throw new RepackException("Missing version entry!"); 188 | } 189 | } 190 | if (check) return null; 191 | if (versionIndex == null) 192 | versionIndex = dirLayout.getVersionIndexFile(realVersion); 193 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading "+realVersion+" manifest..." + ConsoleColors.RESET); 194 | String manifest = Utils.get(verURL); 195 | dirLayout.generateDirsFor(version); 196 | Files.write(versionIndex.toPath(), manifest.getBytes(StandardCharsets.UTF_8)); 197 | return (JsonObject) JsonParser.parseString(manifest); 198 | } 199 | 200 | public Mapping getClientMappings(String version) throws IOException { 201 | File versionMappings = dirLayout.getMappingFile(version, true); 202 | JsonObject jsonObject = getVersionManifest(version); 203 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 204 | return getMappings(versionMappings, downloads.getAsJsonObject("client_mappings").get("url").getAsString(), "client"); 205 | } 206 | 207 | public Mapping getServerMappings(String version) throws IOException { 208 | File versionMappings = dirLayout.getMappingFile(version, false); 209 | JsonObject jsonObject = getVersionManifest(version); 210 | JsonObject downloads = jsonObject.getAsJsonObject("downloads"); 211 | return getMappings(versionMappings, downloads.getAsJsonObject("server_mappings").get("url").getAsString(), "server"); 212 | } 213 | 214 | public Mapping getMappings(File file,String fallBack,String type) throws IOException { 215 | if (file.exists()) { 216 | return new Mapping(Utils.readAll(Files.newInputStream(file.toPath()))); 217 | } 218 | this.out.println(ConsoleColors.YELLOW_BRIGHT + "Downloading "+type+" mappings..." + ConsoleColors.RESET); 219 | String mappings = Utils.get(fallBack); 220 | Files.write(file.toPath(), mappings.getBytes(StandardCharsets.UTF_8)); 221 | return new Mapping(mappings); 222 | } 223 | 224 | public File getClientFile(String version) { 225 | return dirLayout.getMinecraftFile(version, true); 226 | } 227 | 228 | public File getServerFile(String version) { 229 | return dirLayout.getMinecraftFile(version, false); 230 | } 231 | 232 | public File getClientRemappedFile(String version) { 233 | return dirLayout.getMinecraftRepackFile(version, true); 234 | } 235 | 236 | public File getServerRemappedFile(String version) { 237 | return dirLayout.getMinecraftRepackFile(version, false); 238 | } 239 | 240 | public File getClientMappingFile(String version) { 241 | return dirLayout.getMappingFile(version, true); 242 | } 243 | 244 | public File getServerMappingFile(String version) { 245 | return dirLayout.getMappingFile(version, false); 246 | } 247 | 248 | public DirLayout getDirLayout() { 249 | return dirLayout; 250 | } 251 | 252 | public String getLastRelease() { 253 | if (lastRelease == null) { 254 | try { 255 | this.getVersionManifest0("release", true); 256 | } catch (Exception ignored) {} 257 | } 258 | return lastRelease; 259 | } 260 | 261 | public String getLastSnapshot() { 262 | if (lastSnapshot == null) { 263 | try { 264 | this.getVersionManifest0("snapshot", true); 265 | } catch (Exception ignored) {} 266 | } 267 | return lastSnapshot; 268 | } 269 | 270 | public String realVersion(String version) { 271 | switch (version) { 272 | case "20w14∞": 273 | return "20w14infinite"; 274 | case "release": 275 | return this.getLastRelease(); 276 | case "snapshot": 277 | return this.getLastSnapshot(); 278 | default: 279 | return version; 280 | } 281 | } 282 | 283 | public Set getAllCachedVersions() { 284 | return Collections.unmodifiableSet(this.cache.keySet()); 285 | } 286 | 287 | public Set getAllVersions() { 288 | if (lastSnapshot == null) { 289 | try { 290 | this.getVersionManifest0("snapshot", true); 291 | } catch (Exception ignored) {} 292 | } 293 | return this.getAllCachedVersions(); 294 | } 295 | 296 | /** 297 | * Indicate revision of generated bytecode by repacker 298 | * Increment each time the process of repack is modified 299 | */ 300 | public final int repackRevision() { 301 | return Utils.REPACK_REVISION; 302 | } 303 | 304 | private class LogPatcher implements PostPatcher { 305 | private final PostPatcher postPatcher; 306 | private final PostPatcher postPatcherSec; 307 | private final String side; 308 | 309 | private LogPatcher(String side) { 310 | this.postPatcher = null; 311 | this.postPatcherSec = null; 312 | this.side = side; 313 | } 314 | 315 | private LogPatcher(PostPatcher postPatcher,String side) { 316 | this.postPatcher = postPatcher; 317 | this.postPatcherSec = null; 318 | this.side = side; 319 | } 320 | 321 | private LogPatcher(PostPatcher postPatcher, PostPatcher postPatcherSec,String side) { 322 | if (postPatcher == null && postPatcherSec != null) { 323 | this.postPatcher = postPatcherSec; 324 | this.postPatcherSec = null; 325 | } else { 326 | this.postPatcher = postPatcher; 327 | this.postPatcherSec = postPatcherSec; 328 | } 329 | this.side = side; 330 | } 331 | 332 | @Override 333 | public ClassVisitor patch(ClassVisitor classVisitor) { 334 | if (postPatcher != null) { 335 | classVisitor = postPatcher.patch(classVisitor); 336 | if (postPatcherSec != null) { 337 | classVisitor = postPatcherSec.patch(classVisitor); 338 | } 339 | } 340 | return classVisitor; 341 | } 342 | 343 | @Override 344 | public void post(Map remapJar) { 345 | if (postPatcher != null) { 346 | postPatcher.post(remapJar); 347 | if (postPatcherSec != null) { 348 | postPatcherSec.post(remapJar); 349 | } 350 | } 351 | out.println(ConsoleColors.YELLOW_BRIGHT + "Writing "+side+" jar..." + ConsoleColors.RESET);//<3<3<3<3<3 352 | } 353 | 354 | @Override 355 | public void appendManifest(StringBuilder stringBuilder) { 356 | if (postPatcher != null) { 357 | postPatcher.appendManifest(stringBuilder); 358 | if (postPatcherSec != null) { 359 | postPatcherSec.appendManifest(stringBuilder); 360 | } 361 | } 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/main/java/com/fox2code/repacker/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.fox2code.repacker.utils; 2 | 3 | import com.fox2code.repacker.patchers.BytecodeFixer; 4 | import com.fox2code.repacker.patchers.PostPatcher; 5 | import com.fox2code.repacker.rebuild.ClassData; 6 | import com.fox2code.repacker.rebuild.ClassDataProvider; 7 | import org.objectweb.asm.ClassReader; 8 | import org.objectweb.asm.ClassWriter; 9 | import org.objectweb.asm.Opcodes; 10 | import org.objectweb.asm.commons.ClassRemapper; 11 | import org.objectweb.asm.commons.Remapper; 12 | 13 | import java.io.*; 14 | import java.lang.management.ManagementFactory; 15 | import java.net.URL; 16 | import java.net.URLConnection; 17 | import java.nio.charset.StandardCharsets; 18 | import java.nio.file.Files; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.zip.ZipEntry; 23 | import java.util.zip.ZipInputStream; 24 | import java.util.zip.ZipOutputStream; 25 | 26 | public class Utils { 27 | public static final int ASM_BUILD = Opcodes.ASM9; 28 | public static final int REPACK_REVISION = 7; 29 | private static final int THREADS = Math.max(2, 30 | ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors()); 31 | public static boolean debugRemapping = "true".equalsIgnoreCase( 32 | System.getProperty("repacker.debug.remap", System.getProperty("repacker.debug"))); 33 | private static final String charset = "UTF-8"; 34 | public static byte[] cjo; 35 | 36 | static { 37 | try { 38 | InputStream inputStream = Utils.class.getClassLoader().getResourceAsStream("ClientJarOnly.class.repacker"); 39 | if (inputStream == null) { 40 | System.err.println(ConsoleColors.RED_BRIGHT + 41 | "Err: missing /ClientJarOnly.class.repacker" + ConsoleColors.RESET); 42 | } else { 43 | cjo = readAllBytes(inputStream); 44 | } 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | public static String post(String url, String param ) throws IOException { 51 | 52 | URLConnection connection = new URL(url).openConnection(); 53 | connection.setDoOutput(true); // Triggers POST. 54 | connection.setRequestProperty("Accept-Charset", charset); 55 | connection.setRequestProperty("Content-Type", "application/json;charset=" + charset); 56 | 57 | try (OutputStream output = connection.getOutputStream()) { 58 | output.write(param.getBytes(charset)); 59 | } 60 | 61 | InputStream inputStream = connection.getInputStream(); 62 | 63 | return readAll(inputStream); 64 | } 65 | 66 | public static String get(String url) throws IOException { 67 | 68 | URLConnection connection = new URL(url).openConnection(); 69 | connection.setRequestProperty("Accept-Charset", charset); 70 | connection.setRequestProperty("Connection", "keep-alive"); 71 | 72 | InputStream inputStream = connection.getInputStream(); 73 | 74 | return readAll(inputStream); 75 | } 76 | 77 | 78 | public static String readAll(InputStream inputStream) throws IOException { 79 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 80 | 81 | int nRead; 82 | byte[] data = new byte[16384]; 83 | 84 | while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 85 | buffer.write(data, 0, nRead); 86 | } 87 | 88 | inputStream.close(); 89 | 90 | return buffer.toString(charset); 91 | } 92 | 93 | public static byte[] readAllBytes(InputStream inputStream) throws IOException { 94 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 95 | 96 | int nRead; 97 | byte[] data = new byte[16384]; 98 | 99 | while (( nRead = inputStream.read(data, 0, data.length)) != -1) { 100 | buffer.write(data, 0, nRead); 101 | } 102 | 103 | inputStream.close(); 104 | 105 | return buffer.toByteArray(); 106 | } 107 | 108 | public static void download(String url,OutputStream outputStream) throws IOException { 109 | URLConnection connection = new URL(url).openConnection(); 110 | connection.setRequestProperty("Accept-Charset", charset); 111 | connection.setRequestProperty("Connection", "keep-alive"); 112 | 113 | InputStream inputStream = connection.getInputStream(); 114 | 115 | int nRead; 116 | byte[] data = new byte[16384]; 117 | 118 | while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 119 | outputStream.write(data, 0, nRead); 120 | } 121 | 122 | outputStream.flush(); 123 | outputStream.close(); 124 | } 125 | 126 | public static Map readZIP(InputStream in) throws IOException { 127 | ZipInputStream inputStream = new ZipInputStream(in); 128 | Map items = new HashMap<>(); 129 | ZipEntry entry; 130 | while (null!=(entry=inputStream.getNextEntry())) { 131 | if (!entry.isDirectory()) { 132 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 133 | int nRead; 134 | byte[] data = new byte[2048]; 135 | while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 136 | baos.write(data, 0, nRead); 137 | } 138 | items.put(entry.getName(), baos.toByteArray()); 139 | } 140 | } 141 | in.close(); 142 | return items; 143 | } 144 | 145 | public static void writeZIP(Map items, final OutputStream out) throws IOException { 146 | final ZipOutputStream zip = new ZipOutputStream(out); 147 | for (final String path : items.keySet()) { 148 | final byte[] data = items.get(path); 149 | final ZipEntry entry = new ZipEntry(path); 150 | zip.putNextEntry(entry); 151 | zip.write(data); 152 | } 153 | zip.flush(); 154 | zip.close(); 155 | } 156 | 157 | public static void remap(File in, File out, Remapper mapping) throws IOException { 158 | remap(in, out, mapping, null); 159 | } 160 | 161 | public static void remap(File in, File out, Remapper mapping, PostPatcher postPatcher) throws IOException { 162 | if (postPatcher == null) { 163 | postPatcher = PostPatcher.NONE; 164 | } 165 | Map orig = Utils.readZIP(Files.newInputStream(in.toPath())); 166 | ClassDataProvider origCDP = new ClassDataProvider(null); 167 | origCDP.addClasses(orig); 168 | mapping = new CtxRemapper(mapping, origCDP); 169 | Map remap = new ConcurrentHashMap<>(); 170 | Thread[] threads = new Thread[THREADS]; 171 | int t = 0; 172 | final PostPatcher _postPatcher = postPatcher; 173 | final Remapper _mapping = mapping; 174 | String base = Thread.currentThread().getName()+" -> Repacker tId:"; 175 | for (final Map.Entry entry:orig.entrySet()) { 176 | if (entry.getKey().endsWith(".class")) { 177 | if (threads[t] != null) try { 178 | threads[t].join(); 179 | } catch (InterruptedException ie) { 180 | throw new RepackException("Interupted", ie); 181 | } 182 | (threads[t] = new Thread(() -> { 183 | ClassReader classReader = new ClassReader(entry.getValue()); 184 | ClassWriter classWriter = new ClassWriter(classReader, 0); 185 | classReader.accept(new ClassRemapper(new BytecodeFixer(_postPatcher.patch(classWriter)), _mapping), 0); 186 | String name = entry.getKey(); 187 | remap.put(_mapping.map(name.substring(0, name.length()-6))+".class", classWriter.toByteArray()); 188 | }, base+t)).start(); 189 | t = (t+1) % threads.length; 190 | } else if (!entry.getKey().startsWith("META-INF/")) { 191 | remap.put(entry.getKey(), entry.getValue()); 192 | } 193 | } 194 | for (Thread thread:threads) if (thread != null) { 195 | try { 196 | threads[t].join(); 197 | } catch (InterruptedException ie) { 198 | throw new RepackException("Interupted", ie); 199 | } 200 | } 201 | StringBuilder stringBuilder = new StringBuilder( 202 | "Manifest-Version: 1.0\nRepack-Revision: "+ 203 | REPACK_REVISION + "\n"); 204 | postPatcher.appendManifest(stringBuilder); 205 | String text = stringBuilder.toString(); 206 | if (!text.endsWith("\n")) text += "\n"; 207 | remap.put("META-INF/MANIFEST.MF", text.getBytes(StandardCharsets.UTF_8)); 208 | postPatcher.post(remap); 209 | Utils.writeZIP(remap, Files.newOutputStream(out.toPath())); 210 | } 211 | 212 | private static class CtxRemapper extends Remapper { 213 | private final Remapper parent; 214 | private final ClassDataProvider cdp; 215 | 216 | public CtxRemapper(Remapper remapper,ClassDataProvider classDataProvider) { 217 | this.parent = remapper; 218 | this.cdp = classDataProvider; 219 | } 220 | 221 | @Override 222 | public String mapMethodName(String owner, String name, String descriptor) { 223 | return this.mapMethodName(owner, name, descriptor, true); 224 | } 225 | 226 | public String mapMethodName(String owner, String name, String descriptor,boolean root) { 227 | if (name.equals("") || name.equals("") || owner.startsWith("java/") || owner.startsWith("[")) { 228 | return name; 229 | } 230 | final String oldOwner = debugRemapping ? owner : null; 231 | if (owner.endsWith(";")) { 232 | System.out.println("ERROR: "+owner+"."+name+descriptor+" is an invalid method!"); 233 | } 234 | String newName = parent.mapMethodName(owner, name, descriptor); 235 | if (newName.equals(name)) { 236 | for (ClassData classData:cdp.getClassData(owner).getInterfaces()) { 237 | newName = this.mapMethodName(classData.getName(), name, descriptor, false); 238 | if (!newName.equals(name)) { 239 | break; 240 | } 241 | } 242 | } 243 | while (newName.equals(name) && !owner.equals("java/lang/Object")) { 244 | owner = cdp.getClassData(owner).getSuperclass().getName(); 245 | newName = this.mapMethodName(owner, name, descriptor, false); 246 | } 247 | if (oldOwner != null && name.equals(newName) && root) { 248 | System.out.println(ConsoleColors.YELLOW_BRIGHT + "DEBUG: Method resolution failed for -> ("+oldOwner+") "+this.mapType(oldOwner)+"."+name+this.mapDesc(descriptor)+ 249 | (cdp.getClassData(oldOwner).getSuperclass().getName().equals("java/lang/Object") ? " with no parent": "") + ConsoleColors.RESET); 250 | } 251 | return newName; 252 | } 253 | 254 | @Override 255 | public String mapFieldName(String owner, String name, String descriptor) { 256 | return this.mapFieldName(owner, name, descriptor, true); 257 | } 258 | 259 | public String mapFieldName(String owner, String name, String descriptor,boolean root) { 260 | if (owner.startsWith("java/")) { 261 | return name; 262 | } 263 | final String oldOwner = debugRemapping ? owner : null; 264 | String newName = parent.mapFieldName(owner, name, descriptor); 265 | if (newName.equals(name)) { 266 | for (ClassData classData:cdp.getClassData(owner).getInterfaces()) { 267 | newName = this.mapFieldName(classData.getName(), name, descriptor, false); 268 | if (!newName.equals(name)) { 269 | break; 270 | } 271 | } 272 | } 273 | while (newName.equals(name) && !owner.equals("java/lang/Object")) { 274 | owner = cdp.getClassData(owner).getSuperclass().getName(); 275 | newName = this.mapFieldName(owner, name, descriptor, false); 276 | } 277 | if (oldOwner != null && name.equals(newName) && root) { 278 | System.out.println(ConsoleColors.YELLOW_BRIGHT + "DEBUG: Field resolution failed for "+this.mapType(oldOwner)+"#"+name+" "+this.mapDesc(descriptor)+ 279 | (cdp.getClassData(oldOwner).getSuperclass().getName().equals("java/lang/Object") ? " with no parent": "") + ConsoleColors.RESET); 280 | } 281 | return newName; 282 | } 283 | 284 | @Override 285 | public String mapDesc(String descriptor) { 286 | return parent.mapDesc(descriptor); 287 | } 288 | 289 | @Override 290 | public String mapType(String internalName) { 291 | return parent.mapType(internalName); 292 | } 293 | 294 | @Override 295 | public String map(String internalName) { 296 | return parent.map(internalName); 297 | } 298 | } 299 | 300 | /** 301 | * Optimised function to count parameters 302 | * Return -1 if input is invalid 303 | */ 304 | public static int countParms(String desc) 305 | { 306 | int i = 0, p = 0,s = 0; 307 | boolean valid; 308 | char c; 309 | if (desc.charAt(i) == '<') { 310 | i++; 311 | while (i < desc.length()) { 312 | if (desc.charAt(i) == '(') { 313 | break; 314 | } 315 | i++; 316 | } 317 | } 318 | if (desc.charAt(i) != '(') { 319 | return -1; 320 | } 321 | i++; 322 | while ((valid = (desc.length() != i)) && (c = desc.charAt(i)) != ')') 323 | { 324 | if (c != '[') { 325 | p++; 326 | if (c == 'L' || c == 'T') { 327 | i++; 328 | while ((valid = (desc.length() != i)) && ((c = desc.charAt(i)) != ';' || s != 0)) { 329 | if ('<' == c) { 330 | s++; 331 | } 332 | if ('>' == c) { 333 | s--; 334 | if (s < 0) { 335 | return -1; 336 | } 337 | } 338 | i++; 339 | } 340 | if (!valid) { 341 | return -1; 342 | } 343 | } 344 | } 345 | i++; 346 | } 347 | return valid ? p : -1; 348 | } 349 | 350 | public static int countIndexParms(String desc) 351 | { 352 | int i = 0, in = 0,s = 0; 353 | boolean valid; 354 | char c; 355 | if (desc.charAt(i) == '<') { 356 | i++; 357 | while (i < desc.length()) { 358 | if (desc.charAt(i) == '(') { 359 | break; 360 | } 361 | i++; 362 | } 363 | } 364 | if (desc.charAt(i) != '(') { 365 | return -1; 366 | } 367 | i++; 368 | while ((valid = (desc.length() != i)) && (c = desc.charAt(i)) != ')') 369 | { 370 | if (c != '[') { 371 | in++; 372 | if (c == 'L' || c == 'T') { 373 | i++; 374 | while ((valid = (desc.length() != i)) && ((c = desc.charAt(i)) != ';' || s != 0)) { 375 | if ('<' == c) { 376 | s++; 377 | } 378 | if ('>' == c) { 379 | s--; 380 | if (s < 0) { 381 | return -1; 382 | } 383 | } 384 | i++; 385 | } 386 | if (!valid) { 387 | return -1; 388 | } 389 | } else if (c == 'J' || c == 'D'){ 390 | in++; 391 | } 392 | } 393 | i++; 394 | } 395 | return valid ? in : -1; 396 | } 397 | 398 | public static int indexForParm(String desc,int parm) 399 | { 400 | if (parm == 0) return 0; 401 | int i = 1, p = 0,s = 0, in = 0; 402 | boolean valid; 403 | char c; 404 | if (desc.charAt(0) != '(') { 405 | return -1; 406 | } 407 | while ((valid = (desc.length() != i)) && (c = desc.charAt(i)) != ')') 408 | { 409 | if (c != '[') { 410 | p++; 411 | in++; 412 | if (c == 'L') { 413 | i++; 414 | while ((valid = (desc.length() != i)) && ((c = desc.charAt(i)) != ';' || s != 0)) { 415 | if ('<' == c) { 416 | s++; 417 | } 418 | if ('>' == c) { 419 | s--; 420 | if (s < 0) { 421 | return -1; 422 | } 423 | } 424 | i++; 425 | } 426 | if (!valid) { 427 | return -1; 428 | } 429 | } else if (c == 'J' || c == 'D'){ 430 | in++; 431 | } 432 | if (p == parm) { 433 | return in; 434 | } 435 | } 436 | i++; 437 | } 438 | return valid ? in : -1; 439 | } 440 | } 441 | --------------------------------------------------------------------------------