大家好这里是皇鱼
这是本blog的第一篇文章
在这篇文章中,我将教会各位如何下载并安装一个Minecraft版本,并挖掘背后的运行逻辑。
阅读本文前,若您是为了制作一个Minecraft启动器而来,可以查找您的编程语言是否有对应的sdk,这可以帮助您简化工作。
如果你想自己写一个启动核心,那么将会需要本文内容。
本文以C#为例,那么开始。
特别感谢:bmclapi源让我的下载速度提升


获取版本列表

打开Mojang提供的VersionManifest,里面列出了可用的所有版本信息(bmclapi镜像
基本格式为:

    {
        "latest": {
            "release": "1.21",
            "snapshot": "1.21"
        },
        "versions": [
            {
                "id": "1.21",
                "type": "release",
                "url": "https://piston-meta.mojang.com/v1/packages/177e49d3233cb6eac42f0495c0a48e719870c2ae/1.21.json",
                "time": "2024-06-13T08:32:38+00:00",
                "releaseTime": "2024-06-13T08:24:03+00:00"
            },
            {
               ...
            }
        ]
    }

我们需要使用json库提取其中的内容,其中latest中记录了最新的正式版和预览版的版本编号;versions这个json数组里每个json对象都包含id type url time releaseTime这几项,id是这个版本的版本编号,type的值可能为release snapshot old_beta old_alpha,其中release代表这个版本是正式版,snapshot代表是快照版,old_betaold_alpha都是早期远古版。
C#示例代码:

    public static string getVersionManifest()
    {
        using (var client = new HttpClient())
        {
            var response = client.GetAsync("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json").Result;
            response.EnsureSuccessStatusCode();

            var responseContent = response.Content.ReadAsStringAsync().Result;
            return responseContent;
        }
    }
    public static void ParseVersionManifest() {
        string vm = getVersionManifest();
        string lastRelease = GetValueFromJson(vm, "latest.release");
        string lastSnapshot = GetValueFromJson(vm, "latest.snapshot");
        string versionsStr = GetValueFromJson(vm, "versions");
        JsonArray versions = JsonArray.Parse(versionsStr).AsArray();
        Console.WriteLine(lastRelease);
        Console.WriteLine(lastSnapshot);
        foreach ( var version in versions)
        {
            string id = GetValueFromJson(version.ToJsonString(), "id");
            string type = GetValueFromJson(version.ToJsonString(), "type");
            string url = GetValueFromJson(version.ToJsonString(), "url");
            Console.WriteLine($"Version:{id};{type};{url}");
        }
    }

GetVaueFromJson是一个可以通过json和点分path获取具体string值的函数,这里就不放了)
输出:
2024-07-25T16:27:07.png


版本索引

你可能注意到,我们平常游玩时的Minecraft版本是一个包含版本json、版本jar、natives等的文件夹,但上述版本列表中只有一个json的下载链接,那么我们应该如何获得其他的内容的下载链接呢?这就要用到版本索引文件了。
上述链接里的全都是版本索引文件的下载链接,它是一个json,里面包含的内容可以参考wiki
我们需要下载这个json,并将其存储到.minecraft/versions/版本名称(不是id)/版本名称(不是id).json中,例如1.14.4/1.14.4.json
(bmclapi源 将版本信息内的URL中的https://launchermeta.mojang.com/https://launcher.mojang.com/ 替换为 https://bmclapi2.bangbang93.com
接下来,我们需要下载依赖库文件。


下载游戏启动所需的文件

依赖库

依赖库文件的信息存储于版本索引文件中的libraries这个数组中,原版json里,数组的每一项都包含如下内容:

    {
      "downloads": {
        "artifact": {
          "path": "com/github/oshi/oshi-core/6.2.2/oshi-core-6.2.2.jar",
          "sha1": "54f5efc19bca95d709d9a37d19ffcbba3d21c1a6",
          "size": 947865,
          "url": "https://libraries.minecraft.net/com/github/oshi/oshi-core/6.2.2/oshi-core-6.2.2.jar"
        }
      },
      "name": "com.github.oshi:oshi-core:6.2.2"
    }

(部分可能带有classifiers或者rules键,那些是native库,放到后面讲,目前遇到了可以直接存储起来)
我们需要提取出其中的pathurl,如果你想验证文件完整性的话也可以保存sizesha1
path指的是文件保存的路径,以上面为例,那么这个依赖库保存的路径就应该是.minecraft/libraries/com/github/oshi/oshi-core/6.2.2/oshi-core-6.2.2.jar
url就是下载的链接,可以使用bmclapi源下载,即将https://libraries.minecraft.net替换为https://bmclapi2.bangbang93.com/maven
C#示例代码(别抄,这个是同步单线程下载,速度极慢):

    Console.WriteLine("Creating Dirs...");
    Directory.CreateDirectory(@".\.minecraft\versions\1.21");
    Directory.CreateDirectory(@".\.minecraft\libraries");
    if(!File.Exists(@".\.minecraft\versions\1.21\1.21.json")) {
        using (WebClient client = new WebClient())
        {
            client.DownloadFile("https://bmclapi2.bangbang93.com/v1/packages/177e49d3233cb6eac42f0495c0a48e719870c2ae/1.21.json", @".\.minecraft\versions\1.21\1.21.json");
        }
    }

    Console.WriteLine("Downloaded Version Index Json");
    Console.WriteLine("Parsing json to get libraries...");
    string indexJson = File.ReadAllText(@".\.minecraft\versions\1.21\1.21.json");
    string librariesStr = GetValueFromJson(indexJson, "libraries");
    JsonArray larr = JsonArray.Parse(librariesStr).AsArray();
    foreach ( var lib in larr )
    {
        string libStr = lib.ToJsonString();
        string url = GetValueFromJson(libStr, "downloads.artifact.url");
        string path = GetValueFromJson(libStr, "downloads.artifact.path");
        
        if (!File.Exists(@$".\.minecraft\libraries\{path}"))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(@$".\.minecraft\libraries\{path}"));
            
            using (WebClient client = new WebClient())
            {
                client.DownloadFile(url.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"), $@".\.minecraft\libraries\{path}");
                Console.WriteLine($"Downloading library {path} from {url.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven")}");
            }
        }
        
    }
    Console.WriteLine("Libraries Downloaded.");

输出:
2024-07-25T17:47:59.png
2024-07-25T17:48:12.png
目录截图:
2024-07-25T17:48:41.png
2024-07-25T17:49:02.png
至此,依赖库的下载就完成了。

native库

native库只有在游戏启动时才应该被解压,将libraries中含有rulesclassifiers的项下载完成后的对这些文件挨个解压(只解压对应操作系统的,linux就是linux,osx是macos,windows就是windows)里面的所有.dll文件(不需要带上路径),解压到versions/版本名/版本名-natives文件夹中。
C#示例:

       Directory.CreateDirectory(@".\.minecraft\versions\1.21\1.21-natives");
       string indexJson = File.ReadAllText(@".\.minecraft\versions\1.21\1.21.json");
       string librariesStr = GetValueFromJson(indexJson, "libraries");
       JsonArray larr = JsonArray.Parse(librariesStr).AsArray();
       foreach (var lib in larr)
       {
           string libStr = lib.ToJsonString();
           string path = GetValueFromJson(libStr, "downloads.artifact.path");
           if(GetValueFromJson(libStr, "rules[0].os") != null)
           {
               string os = GetValueFromJson(libStr, "rules[0].os");
               if (os.Contains("win"))
               {
                   Console.WriteLine($"Found a windows native {path}, unzipping...");
                   using (ZipArchive archive = ZipFile.OpenRead($@".\.minecraft\libraries\{path}"))
                   {
                       foreach (ZipArchiveEntry entry in archive.Entries)
                       {
                           if (entry.FullName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
                           {
                               string dPath = Path.Combine(@".\.minecraft\versions\1.21\1.21-natives", entry.Name);
                               entry.ExtractToFile(dPath, overwrite: true);
                           }
                       }
                   }
                   Console.WriteLine("unzipped");
               }
           }

       }

解压完后的截图:
2024-07-25T18:08:00.png
这样,natives库也准备好了。

资源索引文件

回到版本索引文件中,里面包含了如下内容:

    "assetIndex": {
        "id": "17",
        "sha1": "fab15439bdef669e389e25e815eee8f1b2aa915e",
        "size": 447033,
        "totalSize": 799252591,
        "url": "https://piston-meta.mojang.com/v1/packages/fab15439bdef669e389e25e815eee8f1b2aa915e/17.json"
    }

这就是资源索引文件的下载方式,同样可以使用上文中的bmclapi来加速下载,我们需要将其存储到.minecraft/assets/indexes中。


由于编写本文时时间太晚了,故下篇文章接着讲下载资源文件。


最后修改:2024-07-26 22:17
本文链接:https://blog.huangyu.win/index.php/archives/3/
版权声明:本文 如何编写一个Minecraft Java版启动器 | Part 1 下载&安装依赖库 | 1-1 为 皇鱼 原创。著作权归作者所有,如无特殊声明,本文将依据CC BY-NC-SA 3.0 CN发布,请注意版权。
转载说明:请依据CC BY-NC-SA 3.0 CN进行转载。
最后修改:2024 年 07 月 26 日
如果觉得我的文章对你有用,请留言/点赞