赞
踩
本教程不适合没有编程基础的人群,请自行绕道
我从来都没写过这种大型教程,所以这篇文章看起来可能会有点乱
发布了发布了,不定期更新
我是百度贴吧的 BLUE_1207,没错,之前skid一个启动器就发出去耀武扬威的小屁孩。今天,我的技术力不同以前了,为了继承前人意志,我准备编写一篇我的世界启动器制作教程。
非常感谢 VEXlife,感谢他发布的我的世界启动器制作教程 (第一版,第二版,第三版),
是他发布的教程让我第一次有兴趣接触面向对象编程,让我有这个机会在这里写文章。所以今天我要在这里详细地解析我的世界的启动流程,并将启动的方法传授给你们,如果你对这方面非常了解,那没事了,是我太菜了。
只要能启动一个进程的就好,比如C#、Java、C++、Qt、易语言(不推荐) 等,你熟悉什么语言就用什么语言编写。这里我会尽量讲原理和贴出C#语言相应的代码,只要你理解了启动的原理,用你熟悉的语言就能写出一款我的世界启动器
现在,让我们开始吧
debug the minecraft
现在,让我们忽略下载游戏和补全资源、库文件,
来了解Minecraft是如何启动的。先使用 HMCL 下载好一个完整1.8.9游戏,并生成启动脚本。
玩得久Minecraft并且常做修改的人应该都知道,
版本的核心文件在.minecraft/version/版本/版本.jar
版本的json文件在.minecraft/version/版本/版本.json
库文件在.minecraft/libraries
文件夹里
资源文件在.minecraft/assets
文件夹里
这些以后都不会再提
使用文本编辑器打开启动脚本,我们可以看到脚本核心部分基本上是这样的格式:
java路径 jvm参数 -cp 库文件路径 主类 游戏参数
你可以在这两个注册表路径
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 库文件路径 主类 游戏参数
而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" }, ... }, ... ] }
像这样的库,有 extract
和 natives
标志的,要按照配置文件的提示找到相应的库解压到游戏版本目录(.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,你可以看到有 mainClass
和 minecraftArguments
,这个是最好处理的,对于原生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来演示,其他语言也是可以的
别催了界面在写了
我们已经靠自己的双手启动好游戏了,现在我们要让程序代替我们的工作,让它来完成这一系列操作,原理都在上面说了,这里就只帖代码了
主要是读注册表
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
分隔字符串就行,不多解释
不细说,这些能让用户输入的东西丢个输入框然后获取值就完事了
先读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或者通过电子信箱联系我,我乐意进行技术交流。如果你是真心想进行技术交流,我相信你能在茫茫因特网中找到我的联系方式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。