├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── cx │ └── y3 │ └── mc │ └── LaunchHelper │ └── Main.java └── resources └── launchhelper.properties /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Codex in somnio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LaunchHelper 2 | 3 | 通过LaunchHelper的Jar可以实现不用添加任何命令行参数载入Java agent并启动另一个可执行Jar,用于在Multicraft面板服使用[authlib-injector](https://github.com/yushijinhun/authlib-injector)。 4 | 5 | ## 使用方法 6 | 7 | 1. 从[Release](https://github.com/Codex-in-somnio/LaunchHelper/releases)获取`LaunchHelper-{版本}.jar`,如果不确定用Windows还是Linux的版本,可以先尝试Linux版本,出错再尝试Windows版本(详见下面的注意事项); 8 | 9 | 2. 将`LaunchHelper-{版本}.jar`放在Minecraft服务端根目录; 10 | 11 | 3. 创建一个命名为`launchhelper.properties`的文件,用于LaunchHelper的配置,填入: 12 | 13 | ``` 14 | javaAgentJarPath= 15 | javaAgentOptions= 16 | execJarPath=<服务端Jar路径> 17 | ``` 18 | 19 | 例如: 20 | 21 | ``` 22 | javaAgentJarPath=authlib-injector.jar 23 | javaAgentOptions=https://example.com/api/yggdrasil/ 24 | execJarPath=paper.jar 25 | ``` 26 | 27 | 也可以直接先启动一次(参考下一步),使样例配置文件自动生成; 28 | 29 | 4. 在面板服上指定由`LaunchHelper-{版本}.jar`启动,或者重命名成自定义服务端需要的特定的文件名,具体需参考面板服方面的说明或咨询面板服客服。 30 | 31 | ## 注意事项 32 | 33 | * LaunchHelper不能跨平台使用,需要使用和平台对应的Jar,跨平台会出现`java.lang.UnsatisfiedLinkError`; 34 | * 如果在Windows环境下用JRE运行,相关动态链接库(`attach.dll`)需要确保能被链接到,经测试发现Windows环境下JRE可能不包含此DLL;遇到这种情况时会出现`no providers installed`的错误消息,可以从JDK安装目录下找到`jre/bin/attach.dll`复制一份到工作目录(服务端根目录)然后启动; 35 | * 目前只测试过Java 8,只有针对Java 8的版本,其他Java版本不保证可用。 36 | 37 | ## 从源码构建 38 | 39 | 可以用Maven构建,环境要求: 40 | 41 | - Java JDK 1.8 42 | - Maven 43 | 44 | 执行以下命令构建: 45 | 46 | ``` 47 | mvn clean package 48 | ``` 49 | 50 | 构建结果位于`target/LaunchHelper-{版本}.jar` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cx.y3.mc 6 | LaunchHelper 7 | 0.1 8 | jar 9 | 10 | LaunchHelper 11 | https://github.com/Codex-in-somnio/LaunchHelper 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 1.8 17 | 18 | 19 | 20 | 21 | com.sun 22 | tools 23 | 1.8 24 | system 25 | ${java.home}/../lib/tools.jar 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-maven-plugin 34 | 1.5.22.RELEASE 35 | 36 | true 37 | true 38 | cx.y3.mc.LaunchHelper.Main 39 | 40 | 41 | 42 | 43 | repackage 44 | build-info 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | src/main/resources 53 | 54 | launchhelper.properties 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main/java/cx/y3/mc/LaunchHelper/Main.java: -------------------------------------------------------------------------------- 1 | package cx.y3.mc.LaunchHelper; 2 | 3 | import com.sun.tools.attach.VirtualMachine; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.lang.management.ManagementFactory; 10 | import java.lang.reflect.Method; 11 | import java.net.URL; 12 | import java.net.URLClassLoader; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.Properties; 16 | import java.util.jar.Attributes; 17 | import java.util.jar.JarFile; 18 | import java.util.jar.Manifest; 19 | 20 | public class Main { 21 | 22 | public static String agentJarPath; 23 | public static String agentOptions; 24 | public static String execJarPath; 25 | 26 | public static void main(String[] args) { 27 | printSplash(); 28 | String propsFileName = "launchhelper.properties"; 29 | File propsFile = new File(propsFileName); 30 | 31 | if (!propsFile.exists()) { 32 | log("Properties file " + propsFileName + " does not exist"); 33 | InputStream examplePropsFileIs = Main.class.getResourceAsStream("/" + propsFileName); 34 | try { 35 | Files.copy(examplePropsFileIs, Paths.get(propsFileName)); 36 | } catch (IOException e) { 37 | log("Failed to generate " + propsFileName); 38 | log(e.getLocalizedMessage()); 39 | return; 40 | } 41 | log("A sample version has been created"); 42 | log("Please modify it as needed."); 43 | return; 44 | } 45 | 46 | try { 47 | Properties props = readPropertiesFile(propsFileName); 48 | agentJarPath = props.getProperty("javaAgentJarPath"); 49 | agentOptions = props.getProperty("javaAgentOptions"); 50 | execJarPath = props.getProperty("execJarPath"); 51 | } catch (IOException e) { 52 | log("Failed to read " + propsFileName); 53 | log(e.getLocalizedMessage()); 54 | return; 55 | } 56 | if (!runJar()) { 57 | log("Failed to launch"); 58 | } 59 | } 60 | 61 | public static Properties readPropertiesFile(String fileName) throws IOException { 62 | FileInputStream fis = null; 63 | Properties prop = null; 64 | fis = new FileInputStream(fileName); 65 | prop = new Properties(); 66 | prop.load(fis); 67 | fis.close(); 68 | return prop; 69 | } 70 | 71 | public static boolean attachJavaAgent() { 72 | log(String.format("Loading Java agent %s=%s", agentJarPath, agentOptions)); 73 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 74 | VirtualMachine vm; 75 | try { 76 | vm = VirtualMachine.attach(pid); 77 | } catch (Exception e) { 78 | log("Failed to attach to the JVM"); 79 | log(e.getLocalizedMessage()); 80 | return false; 81 | } 82 | try { 83 | vm.loadAgent(agentJarPath, agentOptions); 84 | vm.detach(); 85 | } catch (Exception e) { 86 | log(e.getLocalizedMessage()); 87 | return false; 88 | } 89 | return true; 90 | } 91 | 92 | public static boolean runJar() { 93 | log("Loading executable Jar " + execJarPath); 94 | JarFile jarFile; 95 | try { 96 | jarFile = new JarFile(execJarPath); 97 | } catch (IOException e) { 98 | log("Failed to open the specified executable jar"); 99 | log(e.getLocalizedMessage()); 100 | return false; 101 | } 102 | Manifest manifest; 103 | Attributes attributes; 104 | String className; 105 | try { 106 | manifest = jarFile.getManifest(); 107 | attributes = manifest.getMainAttributes(); 108 | className = attributes.getValue(Attributes.Name.MAIN_CLASS); 109 | jarFile.close(); 110 | } catch (IOException e) { 111 | log("Failed to get manifest from the specified executable jar"); 112 | log(e.getLocalizedMessage()); 113 | return false; 114 | } 115 | 116 | File file = new File(execJarPath); 117 | try { 118 | URL[] urls = { file.toURI().toURL() }; 119 | URLClassLoader loader = new URLClassLoader(urls); 120 | Class cls = loader.loadClass(className); 121 | Method mcMain = cls.getDeclaredMethod("main", String[].class); 122 | String[] mcArgs = {}; 123 | 124 | ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); 125 | Thread.currentThread().setContextClassLoader(cls.getClassLoader()); 126 | 127 | if (!attachJavaAgent()) { 128 | log("Failed to attach Java agent"); 129 | loader.close(); 130 | return false; 131 | } 132 | mcMain.invoke(null, (Object) mcArgs); 133 | 134 | Thread.currentThread().setContextClassLoader(oldCl); 135 | 136 | loader.close(); 137 | } catch (Exception e) { 138 | throw new RuntimeException(e); 139 | } 140 | return true; 141 | } 142 | 143 | public static void printSplash() { 144 | log(""); 145 | log(" // //// // // // // ///// // //"); 146 | log(" // // // // // /// // // // // ___"); 147 | log(" // ////// // // // /// // ////// ____"); 148 | log(" // // // // // // // // // // _____"); 149 | log("////// // // //// // // ///// // // ______"); 150 | log(""); 151 | log(" // // ////// // ///// ////// /////"); 152 | log(" // // // // // // // // // ___"); 153 | log(" ////// ///// // ///// ///// ///// ____"); 154 | log(" // // // // // // // // _____"); 155 | log(" // // ////// ////// // ////// // // ______"); 156 | log(""); 157 | 158 | Properties buildInfo = new Properties(); 159 | try { 160 | InputStream biif = Main.class.getResourceAsStream("/META-INF/build-info.properties"); 161 | if (biif == null) 162 | throw new IOException(); 163 | buildInfo.load(biif); 164 | } catch (IOException e) { 165 | log("Failed to read build information: " + e.getLocalizedMessage()); 166 | } 167 | String name = "LaunchHelper"; 168 | String version = buildInfo.getProperty("build.version"); 169 | String info = String.format("%s (%s) Created with ♥ by Codex in Somnio", name, version); 170 | String rule = ""; 171 | for (int i = 0; i < info.length(); ++i) 172 | rule += "="; 173 | log(rule); 174 | log(info); 175 | log("https://github.com/Codex-in-somnio/LaunchHelper"); 176 | log(rule); 177 | log(""); 178 | } 179 | 180 | public static void log(String message) { 181 | System.out.println("[LH] " + message); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/resources/launchhelper.properties: -------------------------------------------------------------------------------- 1 | javaAgentJarPath=authlib-injector.jar 2 | javaAgentOptions=https://example.com/api/yggdrasil/ 3 | execJarPath=paper.jar --------------------------------------------------------------------------------