我的世界启动器制作教程

碎碎念

本教程不适合没有编程基础的人群,请自行绕道

我从来都没写过这种大型教程,所以这篇文章看起来可能会有点乱

发布了发布了,不定期更新

为什么我要写这篇文章

我是百度贴吧的 BLUE_1207,没错,之前skid一个启动器就发出去耀武扬威的小屁孩。今天,我的技术力不同以前了,为了继承前人意志,我准备编写一篇我的世界启动器制作教程。
非常感谢 VEXlife,感谢他发布的我的世界启动器制作教程 (第一版第二版第三版),
是他发布的教程让我第一次有兴趣接触面向对象编程,让我有这个机会在这里写文章。所以今天我要在这里详细地解析我的世界的启动流程,并将启动的方法传授给你们,如果你对这方面非常了解,那没事了,是我太菜了。

使用什么语言编写最好?

只要能启动一个进程的就好,比如C#、Java、C++、Qt、易语言(不推荐) 等,你熟悉什么语言就用什么语言编写。这里我会尽量讲原理和贴出C#语言相应的代码,只要你理解了启动的原理,用你熟悉的语言就能写出一款我的世界启动器

现在,让我们开始吧
debug the minecraft

了解Minecraft是如何启动的

现在,让我们忽略下载游戏和补全资源、库文件,
来了解Minecraft是如何启动的。先使用 HMCL 下载好一个完整1.8.9游戏,并生成启动脚本。
玩得久Minecraft并且常做修改的人应该都知道,
版本的核心文件在.minecraft/version/版本/版本.jar
版本的json文件在.minecraft/version/版本/版本.json
库文件在.minecraft/libraries文件夹里
资源文件在.minecraft/assets文件夹里
这些以后都不会再提
使用文本编辑器打开启动脚本,我们可以看到脚本核心部分基本上是这样的格式:
java路径 jvm参数 -cp 库文件路径 主类 游戏参数

获取 Java 路径

你可以在这两个注册表路径
HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment
HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit
找到Java的路径,在它里面Java版本的JavaHome值,读取该值然后加上\bin\java.exe再套上双引号再双写\来转义就完事了
示例值 C:\Program Files\Java\jre1.8.0_271

Java路径问题已解决
当前启动脚本: “C:\\Program Files\\Java\\jre1.8.0_271\\bin\\java.exe” jvm参数 -cp 库文件路径 主类 游戏参数

Jvm参数

而HMCL的jvm参数如下

-Dminecraft.client.jar=.minecraft\versions\1.8.9\1.8.9.jar
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
-XX:G1NewSizePercent=20
-XX:G1ReservePercent=20
-XX:MaxGCPauseMillis=50
-XX:G1HeapRegionSize=16M
-XX:-UseAdaptiveSizePolicy
-XX:-OmitStackTraceInFastThrow
-Xmn128m
-Xmx1920m
-Dfml.ignoreInvalidMinecraftCertificates=true
-Dfml.ignorePatchDiscrepancies=true
-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump
-Djava.library.path=D:\HMCL.minecraft\versions\1.8.9\natives
-Dminecraft.launcher.brand=HMCL
-Dminecraft.launcher.version=3.3.xwx

这些参数指定了可优化jvm的参数、最大最小运行内存、启动器品牌信息等等,对于启动mc比较基础的一般只有 -Xmx-Djava.library.path=链接库路径(路径有空格要双写\并加双引号,懒得判断可以全部加),其他参数酌情增加

Jvm参数问题已解决
当前启动脚本: “C:\\Program Files\\Java\\jre1.8.0_271\\bin\\java.exe” -Xmx1024M “-Djava.library.path=D:\\HMCL\\.minecraft\\versions\\1.8.9\\natives” -cp 库文件路径 主类 游戏参数

库文件路径

可以通过版本json文件的 libraries:[] 来获取到所有库文件的绝对路径
现在以1.8.9的json为例子,取其中一项来分析

{
  ...
  "libraries": [
    {
      "name": "com.mojang:netty:1.6",
      "downloads": {
        "artifact": {
          "path": "com/mojang/netty/1.6/netty-1.6.jar",
          "url": "https://libraries.minecraft.net/com/mojang/netty/1.6/netty-1.6.jar",
          "sha1": "4b75825a06139752bd800d9e29c5fd55b8b1b1e4",
          "size": 7877
        }
      }
    },
    ...
  ]
}

现在我们分析 com.mojang:netty:1.6,这个库你可以用搜索找到它的路径在 com\mojang\netty\1.6\netty-1.6.jar,所以我们只要有 name 就能够通过字符串拼接来拼出库文件的路径
先将name以:分割,把第一部分的.替换成\,然后这样拼接

库文件文件夹\第一部分\第二部分\第三部分\第二部分-第三部分.jar

一个库文件的绝对路径就拼接出来了
有一些库比较特殊,比如

{
  ...
  "libraries": [
        {
          "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
          "downloads": {
          ...
          },
          "extract": {
            "exclude": [
              "META-INF/"
            ]
          },
          "natives": {
            "linux": "natives-linux",
            "osx": "natives-osx",
            "windows": "natives-windows"
          },
          ...
        },
    ...
  ]
}

像这样的库,有 extractnatives 标志的,要按照配置文件的提示找到相应的库解压到游戏版本目录(.minecraft/versions/版本/)的 natives 文件夹里,那个文件夹就是前面jvm路径提到的链接库路径
我们跟前面一样顺藤摸瓜可以找到 org\lwjgl\lwjgl\lwjgl-platform\2.9.4-nightly-20150209 里有一个文件名叫
lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar
前面的路径容易拼接,后面的文件名和以前不同了
.jar前多了-native-windows
那我们在寻找需要解压的库的时候只要拼接这个路径即可

库文件文件夹\第一部分\第二部分\第三部分\第二部分-第三部分-平台.jar

平台对应字符串在json的 natives 那里可以获取到
现在,你要遍历libraries里所有项,以;为分隔符拼接所有的绝对路径

库文件路径问题已解决
当前启动脚本: “C:\\Program Files\\Java\\jre1.8.0_271\\bin\\java.exe” -Xmx1024M " -Djava.library.path=D:\\HMCL\\.minecraft\\versions\\1.8.9\\natives" -cp “D:\\HMCL\\libraries\\com\\mojang\\netty\\1.6\\netty-1.6.jar;D:\(后省略)” 主类 游戏参数

游戏参数

现在我们再用文本编辑器打开游戏json,你可以看到有 mainClassminecraftArguments,这个是最好处理的,对于原生Minecraft启动,你可以直接把 mainClass 填进主类里,而 minecraftArguments 需要替换一些字符串。
从json里复制出来的是这样的

"minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type}",

再对照下启动脚本,所以,在把游戏参数填进启动脚本之前,我们需要

${auth_player_name} 替换成 玩家名
${version_name} 替换成 任意字符,可以是启动器的品牌信息如HMCL
${game_directory} 替换成 游戏.minecraft绝对路径
${assets_root} 替换成 资源文件绝对路径
${assets_index_name} 替换成 json里assetIndex->id的值,如1.8
${auth_uuid} 替换成 玩家uuid<-正版登录需要,离线模式可填写随机的
${auth_access_token} 替换成 玩家令牌<-正版登录需要,离线模式可填uuid
${user_properties} 替换成 用户json配置<-正版登录需要,离线模式可填{}
${user_type} 替换成 用户类型 | 类型有正版登录mojang和离线模式legacy
其实没有令牌登录不了自动进离线模式,懒得话一律填写mojang

你还可以添加一些额外参数,比如
--width 窗口宽度
--height 窗口高度
--fullscreen 全屏
--demo 试玩模式
--server 服务器ip --port 服务器端口 启动后进服

主类/游戏参数问题已解决
当前启动脚本: “C:\\Program Files\\Java\\jre1.8.0_271\\bin\\java.exe” -Xmx1024M " -Djava.library.path=D:\\HMCL\\.minecraft\\versions\\1.8.9\\natives" -cp “D:\\HMCL\\libraries\\com\\mojang\\netty\\1.6\\netty-1.6.jar;D:\(后省略)” 主net.minecraft.client.main.Main --username LittleCatX --version “HMCL 3.3.181” --gameDir “D:\\HMCL\\.minecraft” --assetsDir “D:\\HMCL\\.minecraft\\assets” --assetIndex 1.8 --uuid b28e05e703a236e5b101e61761ec80ee --accessToken 41495b2588624230beba0583a1505d7b --userProperties {} --userType mojang --width 854 --height 480

敲到cmd里启动试试吧!

编写自动化启动过程

这样启动游戏是不是太慢了呢,要一个一个库文件拼接,人工操作要多久才能完成啊
现在,把这些操作用程序批量完成吧

选择语言

在碎碎念已经说过了,在这里我将使用C# WPF来演示,其他语言也是可以的
别催了界面在写了

从头来过

我们已经靠自己的双手启动好游戏了,现在我们要让程序代替我们的工作,让它来完成这一系列操作,原理都在上面说了,这里就只帖代码了

获取Java路径

主要是读注册表

        public enum JavaType
        {
            jre,jdk,both
        }
        
        public static List<String> getJavaList(JavaType type)
        {
            List<String> java = new List<string>();
            RegistryKey hkml = Registry.LocalMachine;
            RegistryKey software = hkml.OpenSubKey("SOFTWARE", true);
            RegistryKey javasoft = software.OpenSubKey("JavaSoft", true);
            if (type.Equals(JavaType.jre) || type.Equals(JavaType.both))
            {
                RegistryKey jre = javasoft.OpenSubKey("Java Runtime Environment", true);
                foreach (String name in jre.GetSubKeyNames())
                {
                    if (name.Contains(":")) continue;
                    RegistryKey jre_ = jre.OpenSubKey(name, true);
                    String javahome = (String)jre_.GetValue("JavaHome", "none");
                    java.Add("jre:" + name + ":" + javahome);
                }
            }
            if (type.Equals(JavaType.jdk) || type.Equals(JavaType.both))
            {
                RegistryKey jdk = javasoft.OpenSubKey("Java Development Kit", true);
                foreach (String name in jdk.GetSubKeyNames())
                {
                    if (name.Contains(":")) continue;
                    RegistryKey jdk_ = jdk.OpenSubKey(name, true);
                    String javahome = (String)jdk_.GetValue("JavaHome", "none");
                    java.Add("jdk:" + name + ":" + javahome);
                }
            }
            return java;
        }

这样你就能获取到Windows电脑上安装的Java列表了,单个java返回的格式是

类型:名称:路径

jdk:1.8.0_191:C:\Program Files\Java\jdk1.8.0_191

分隔字符串就行,不多解释

Jvm 参数

不细说,这些能让用户输入的东西丢个输入框然后获取值就完事了

拼接库文件路径

先读json,我这里用的是CsharpJson,就四个类,直接丢到项目里就完事了。
别问我为什么不用 Newtonsoft,因为我不会打包到exe里。
mcbbs 的启动器要求是单文件不给带dll。
用 VEXlife 的解包 7zip 解压法会让exe空间占用大到1MB
mcbbs 的启动器要求单文件不能太大,要是以后需要美化又不够空间了就凉了,通过百度我找到了使用 shell32.dll 解压的办法,拼接库文件和解压动态链接库的代码如下:


        /// <summary>
        /// 解压文件到目录
        /// 需要引用动态链接库 C:\Windows\System32\shell32.dll
        /// </summary>
        /// <param name="inputFileName"></param>
        /// <param name="outputDirName"></param>
        /// <returns></returns>
        public static bool decompress(String inputFileName, String outputDirName)
        {
            if (!File.Exists(inputFileName)) return false;
            if (File.Exists(inputFileName + ".zip")) File.Delete(inputFileName + ".zip");
            File.Copy(inputFileName, inputFileName + ".zip");
            try
            {
                Shell32.Shell sc = new Shell32.Shell();
                Shell32.Folder SrcFolder = sc.NameSpace(inputFileName + ".zip");
                Shell32.Folder DestFolder = sc.NameSpace(outputDirName);
                Shell32.FolderItems items = SrcFolder.Items();
                DestFolder.CopyHere(items, 20);
                if(File.Exists(inputFileName + ".zip")) File.Delete(inputFileName + ".zip");
                return true;
            }
            catch
            {
                if (File.Exists(inputFileName + ".zip")) File.Delete(inputFileName + ".zip");
                return false;
            }
        }

        public static String splicLibraries(String jsonString, String versionLibrariesPath, String versionNativePath)
        {
            String result = "";
            JsonDocument doc = JsonDocument.FromString(jsonString);
            if (doc.IsObject())
            {
                JsonArray libs = doc.Object["libraries"].ToArray();
                for (int i = 0; i < libs.Count; i++)
                {
                    JsonObject lib = libs[i].ToObject();
                    string name = lib["name"].ToString();
                    if (name.Contains(":"))
                    {
                        string[] nameArray = name.Split(':');
                        if (nameArray.Length == 3)
                        {
                            String p= "\\\\" + nameArray[0].Replace(".", "\\\\") + "\\\\" +
                                nameArray[1] + "\\\\" +
                                nameArray[2] + "\\\\" +
                                nameArray[1] + "-" + nameArray[2] + ".jar";
                            result += versionLibrariesPath + p + (i + 1 < libs.Count ? ";" : "");
                            // DEBUG
                            // Console.WriteLine("添加库 " + p);
                            if (lib.ContainsKey("natives"))
                            {
                                String path = versionLibrariesPath + "\\" + nameArray[0].Replace(".", "\\") + "\\" +
                                    nameArray[1] + "\\" +
                                    nameArray[2] + "\\" +
                                    nameArray[1] + "-" + nameArray[2] + "-" + lib["natives"].ToObject()["windows"].ToString()
                                    .Replace("${arch}", (Environment.Is64BitOperatingSystem ? "64" : "32")) + ".jar";
                                // DEBUG
                                // Console.WriteLine("正在解压库 " + path);
                                // Console.WriteLine("解压" + (decompress(path, versionNativePath) ? "成功" : "失败"));
                            }
                        }
                    }
                }
            }
            else
            {
                return "null";
            }
            return "\"" + result + "\"";
        }

我在找解压方法的时候看到了用 shell32.dll 解压的方法,据说比较慢,但是分隔库文件加解压我这里测试总共用的时间不超过10秒钟,解压这么小的文件应该还是凑合用得上的。
要用 shell32.dll 来解压,需要在项目->引用->添加引用

在浏览处选择文件 C:\Windows\System32\shell32.dll 并确定即可

只要再做一点小小的修改,就能让程序判断哪些库不存在,然后让程序主动去下载,代码在下次更新的时候再补上

下载游戏

TODO

补全资源

TODO

更多功能

TODO

停更?

这篇文章有可能不会更新了,本是我一时兴起想好好深究启动器的编写并做经验分享。世事难料,从零开始,不依赖所谓"启动模块",写启动器是周期非常长的,而且极其费力不讨好。如果你想为Minecraft服务器客户端做一个独特的启动器,我的建议是找一个开源的启动器,fork它,修改,并开放源代码。目前我就是这样做的,对于短期想要获得一个精美且稳定的启动器来说非常适合,当然,这需要较中等的编程水平以至于你能理解如何修改代码。

我的服务器客户端启动器基于HMCL,现在它自带了一套默认主题,第一次打开时附加了服务器使用协议(规则)的提示,公共配置与HMCL独立,并且将测试版提示做成了类似PCL的主菜单自定义组件,可以加图和自定义操作的链接或按钮。并且修改了c++壳,默认扫的当前目录java路径改为了 .minecraft/java/,以便客户端优雅地内置java,即装即用。

这么做有几点需要注意,你可以给启动器改名,你可以在作者中加上你,但你必须不能修改原作者的版权信息,不能模糊或否认你的启动器基于原启动器的事实,这是对原作者最基本的尊重。

我不经常上CSDN,如果你在启动器编写上遇到了问题,可以加我的QQ或者通过电子信箱联系我,我乐意进行技术交流。如果你是真心想进行技术交流,我相信你能在茫茫因特网中找到我的联系方式。

  • 19
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒怠的小猫Official

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值