使用C++編寫Java Minecraft Launcher 啓動器 —— 定義

545天前 · 程式設計 · c++ · 2580次阅读

製作麥塊/我的世界啓動器

Java Minecraft Edition是最常見的Mc版本,其遊戲在設計時將遊戲分爲了⌈遊戲本體⌋與⌈啓動器⌋兩個部分,這使我們可以透過自行編寫啓動器支援第三方內容。

啓動器的基本原理

Minecraft透過 Java 虛擬機(JVM)運行遊戲,並需要附加一些遊戲的啓動參數。這些參數控制了 Minecraft 的行爲,如用戶資訊與遊戲資源。

就像運行普通的 Java 程式一樣,我們需要準備 JVM 參數遊戲參數,並透過指令將它們傳遞給 Java 執行。

簡單範例

在C++中我們可以透過std::system()函式來執行shell命令,從而調用 JVM 來啟動 Minecraft。
啓動命令大致如下:

javapath -Xmx2G -cp "minecraft.jar" net.minecraft.client.main.Main --username <username> --version <version> --accessToken <access_token> --userProperties {} --assetsDir <assets_dir> --assetIndex <asset_index> --gameDir <game_dir> --width 854 --height 480
  • Xmx2G JVM參數,設定該虛擬機可用的最大記憶體。
  • "minecraft.jar" 遊戲的主jar檔。
  • net.minecraft.client.main.Main 主類名,設定該java程式的入口。
  • --username--version--accessToken等則是遊戲本體的參數。

程式碼範例(非完整):

#include <iostream>
#include <string>
#include <cstdlib>

void launchMinecraft(const std::string& javaPath, const std::string& gameDir, const std::string& username, const std::string& accessToken) {
    std::string command = javaPath + " -Xmx2G -cp \"" + gameDir + "/minecraft.jar\" net.minecraft.client.main.Main "
                          "--username " + username +
                          " --version latest " + ...
                          " --accessToken " + accessToken +
                          " --gameDir " + gameDir +
                          " --width 854 --height 480";

    std::cout << "Launching Minecraft with command: " << command << std::endl;
    std::system(command.c_str());
}

int main() {
    std::string javaPath = "C:/Program Files/Java/jre1.8.0_251/bin/java";
    std::string gameDir = "C:/Minecraft";
    std::string username = "playerName";
    std::string accessToken = "abcdefg123456789";

    launchMinecraft(javaPath, gameDir, username, accessToken);
    return 0;
}

構建命令

我們如何構建命令?

通常來說,遊戲的啓動參數都存儲在相應的版本資料夾中。
列如: /to/path/.minecraft/versions/VerName/VerName.json
其使用json格式存儲資訊:

檔案結構

版本資訊json

| 鍵名 | 類型 | 描述 |
| - | - | - |
| arguments | Map | 參數列表,分爲jvmgame參數,分別又爲兩個子列表。|
| assetIndex | Map | 該版本的資源索引資訊,包括版本id(字串,標識符)、url(字串)、sha1(字串)、size(整型)、totalSize(整型,所有資源總大小)等鍵。|
| javaVersion | Map | 要求的Java資訊,如:majorVersion(推薦JAVA版本)等。|
| libraries | Map | 遊戲依賴的程式庫,包括下載資訊。|
| downloads | Map | 客戶端服務端的下載資訊。存儲在client與server鍵中,包括urlsha1size鍵。
| logging | Map | log4j設定檔。|
| id | 字串 | 標識符版本名|
| mainClass | 字串 | 主類名|
| jar | 字串 | 客戶端主檔名|
| assets | 字串| 資源版本|
| type | 字串 | 版本類型,可以是:release(正式版)、snapshot(快照)、old_beta(Beta版)或old_alpha(Alpha版)。|
| time | 字串 | 版本更新時間|
| releaseTime | 字串 | 版本發佈時間|
| minimumLauncherVersion | 整型| 要求的最低官方啓動器版本|
| patches | Map | 非官方結構,某些啓動器可能會用於存儲原版的啓動資訊。

規則(rules)

argumentslibraries每項都可能包含一個名爲rules的Map:

| 鍵名 | 類型 |描述 |
| - | - | - |
| action | 字串 | allow(允許) 或 disallow(不允許),如果滿足規則中的其餘條件則執行。
| features | Map | 對應啓動器內設定,通常是一個布林變數。如:is_demo_user
| os | Map | 系統要求,name(系統名稱,osxwindows,linux)、version(系統版本)、和arch(系統架構,x86、arm)。

範例:

{
      "name": "org.lwjgl:lwjgl-glfw:3.2.2",
      "downloads": {
        "artifact": {
          "path": "org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar",
            ...
        },
        ...
      "rules": [
        {
          "action": "allow"
        },
        {
          "action": "disallow",
          "os": {
            "name": "osx"
          }
        }
      ]
}

按照上述rules,除了osx不添加該物件,其餘系統都應添加。

僅某個系統允許:

      "rules": [
        {
          "action": "allow",
          "os": {
            "name": "osx"
          }
        }
      ]

依賴程式庫

libraries中每個對象都應有namedownloads鍵,但需要注意downloads鍵值有可能爲(無鍵值)。
name遵循特定規則命名:

<package>:<name>:<version>

package為完整包名,name為庫名,而version為庫版本號。

普通依賴程式庫的資訊存儲於libraries[i].downloads.artifact

|鍵名|類型|描述|
|-|-|-|
|path|字串|該程式庫相對與libraries資料夾的路徑,包括副檔名。|
|url|字串|下載該程式庫的完整url。|
|sha1|字串|雜湊值|
|size|整型|該檔案的大小|

由於downloads並非一定存在,所以應當以name鍵構建路徑。

packagename中的.在構建路徑時應替換爲資料夾層級/,版本中的保持不變。檔案名稱則爲<name>-<version>.jar

舉例來說,nameorg.ow2.asm:asm:9.1解析路徑爲:org/ow2/asm/asm/9.1/asm-9.1.jar

一步步來,org.ow2.asm是包,其中的.應替換爲資料夾層級,org/ow2/asm

再把:替換爲/就得到了路徑,org/ow2/asm/asm/9.1,加上檔案名:/asm-9.1.jar鍵就得到最終路徑:org/ow2/asm/asm/9.1/asm-9.1.jar了。

當然,你還需要拼接libraries的絕對路徑才是完整。列如:/to/path/.minecraft/libraries/org/ow2/asm/asm/9.1/asm-9.1.jar

natives程式庫

natives用來支援特定平台,其可以透過libraries[i].downloads.natives確定,如果沒有該鍵則認爲不存在。

natives存儲於libraries[i].downloads.classifiers.<natives_key>。
<natives_key>為natives鍵中系統對應的值,範例:

    {
      "name": "org.lwjgl:lwjgl-jemalloc:3.2.1",
      "downloads": {
        "artifact": {
            ...
        },
        "classifiers": {
          "natives-macos": {
            ...
          }
        }
      },
      "natives": {
        "osx": "natives-macos"
      },

libraries[i].downloads.classifiers.natives-macos爲該natives程式庫資訊。
它的檔案路徑與一般程式庫大致相同,只是檔案名爲:

<name>-<version>-<natives_key>.jar

也就是說,如果一個程式庫路徑爲org/lib/hi-lib.jar並且存在對應系統的netives庫,那麼其路徑應爲:org/lib/hi-lib-netives-macos.jar

啓動參數解析

啟動參數將傳入javajavaw可執行檔,並分為JVM參數Minecraft參數兩部分。

注,一般你只需要從版本檔案中解析出參數並替換相應參數即可。無需手動關心添加。

以下爲參數解析:

JVM參數

  • -X-XX:組態JVM,如GC。

    • -Xmx1024m 設定該虛擬機可用的最大記憶體為1024MB。
    • -XX:-OmitStackTraceInFastThrow 省略異常棧資訊從而快速拋出。
  • -D:組態JVM系統屬性,格式為-D<name>=<value>

    • -Dos.name=Windows 10 -Dos.version=10.0設定系統名與版本。
    • -Dminecraft.launcher.brand=NekoLauncher -Dminecraft.launcher.version=1.0.1 目前啓動器的名稱與版本。如果有空格或特殊字符,需要使用引號包裹。
    • -Dlog4j.configurationFile=<檔案路徑> e.g /client-1.12.xml 遊戲日誌設定檔
    • -Djava.library.path=<natives資料夾路徑> 當前系統下遊戲運行所需的natives程式庫。
  • -cp:Class Path

    • 所有當前版本Minecraft的普通庫檔案路徑及遊戲主檔,路徑在Windows使用;分隔,Unix使用:

arguments.jvm中可能需要的參數,在解析命令時替換:
|鍵名|描述|
|-|-|
|${natives_directory}| natives程式庫的資料夾,官方啓動器會將其解壓至臨時資料夾,你可以自行拷貝程式庫到固定位置。|
|${launcher_name}|啓動器名稱,包括特殊字串請使用引號包裹。|
|${launcher_version}|啓動器版本,類型爲字串。|
|${classpath}|遊戲主檔(如1.20.jar)與所有一般程式庫絕對路徑。|
|${library_directory}|libraries資料夾的絕對路徑。

遊戲參數

以主類名開頭,通常為net.minecraft.client.main.Main,若安裝Mod載入器則一般為net.minecraft.launchwrapper.Launchcpw.mods.modlauncher.Launcher
你只需要關心遊戲版本檔中獲取即可。
常見參數:

  • --username 後接使用者名稱。
  • --version 後接遊戲版本。
  • --gameDir 後接遊戲路徑。
  • --assetsDir 後接資源檔案路徑。
  • --assetIndex 後接資源索引版本。
  • --uuid 後接使用者UUID。
  • --accessToken 後接登入權杖。
  • --userType 後接使用者類型。
  • --versionType 後接版本類型,會顯示在遊戲主介面右下角。
  • --width 後接視窗寬度。
  • --height 後接視窗高度。
  • --server 後接伺服器位址,遊戲進入時將直接連入伺服器
  • --port 後接伺服器的埠號
  • ${**} 由啓動器填入的參數

arguments.game中可能需要的參數,在解析命令時替換:
|鍵名|描述|
|- | -|
|${auth_player_name}|使用者名稱|
|${version_name}|版本名稱,可任意字串|
|${game_directory}|遊戲的絕對路徑,如:/to/path/.minecraft
|${assets_root}|資源檔的根路徑,如:/to/path/.minecraft/assets
|${assets_index_name}|資源版本,如1.20,解析assets鍵填入。
|${auth_uuid}|uuid
|${auth_access_token}|登入權杖Token
|${user_type}|mojang
|${version_type}|版本類型,可任意字串

在C++中實現

可以參考我已經在Neko Launcher中實現的部分。

後面我將會再編寫一篇新的文章專門講解在C++中實現的思路,以下是大致思路解決方案:

我們假定遊戲資料夾位於/to/path/.minecraft/

讓我們獲取遊戲版本:

//使用資料夾迭代,檢查資料夾中的所有內容
for (const auto &it : std::filesystem::directory_iterator("/to/path/.minecraft/versions/")) {
    //如果是目錄,則代表這是一個遊戲版本
    if (it.is_directory()) {
        verDir =  std::filesystem::absolute(it).string();//取得絕對路徑
        verFile = verDir + "/" + it.path().filename().string() + ".json";//資料夾名即爲版本檔案名
    }
}
解析json
...

檢查條件(第三方程式庫與定義參閱原始碼):

        auto checkCondition = [=](const RulesMap &rules, const nlohmann::json &features) -> bool {
            if (!features.empty()) {
                if (features.contains("is_demo_user") && features["is_demo_user"].get<bool>() == isDemoUser)
                    return true;//僅鍵存在且與變數isDemoUser相同的情況下返回true。
                if (features.contains("has_custom_resolution") && features["has_custom_resolution"].get<bool>() == hasCustomResolution)
                    return true;
            }

            if (!rules.osName.empty()) {
                bool allow = (rules.osName == info::getOsNameS() && rules.action == "allow") || (rules.osName != info::getOsNameS() && rules.action == "disallow");
                if (allow)//系統名相同且規則爲允許,或不相同且規則爲禁止時返回true
                    return true;
            }

            if (!rules.osArch.empty()) {
                bool allow = (rules.osArch == osArch && rules.action == "allow") || (rules.osArch != osArch && rules.action == "disallow");
                if (allow)
                    return true;
            }

            return false;
        };

processArgs思路:
迭代arguments.jvmarguments.game,判斷爲字串則直接添加。
如果爲Map或物件,則嘗試迭代rules

name構造路徑:
使用find查找三次:或使用正則std::regex("([^:]+):([^:]+):([^:]+)")捕獲。
然後拼接:

return package + "/" + name + "/" + version + "/" + name + "-" + version + ".jar";

替換參數:

        // replace placeholders
        auto replacePlaceholders = [&](std::vector<std::string> &argsVec, const std::map<std::string, std::string> &placeholders) {
            for (auto &arg : argsVec) {
                for (const auto &[key, value] : placeholders) {
                    std::string::size_type pos;
                    while ((pos = arg.find(key)) != std::string::npos) {
                        arg.replace(pos, key.length(), value);
                    }
                }
            }
        };

        // jvm
        replacePlaceholders(jvmArgsVec, {{"${natives_directory}", nativesPath},
                                         {"${library_directory}", librariesPath},
                                         {"${launcher_name}", "Neko Launcher"},
                                         {"${launcher_version}", info::getVersion()},
                                         {"${classpath}", classPath}});

        // game
        replacePlaceholders(gameArgsVec, {{"${auth_player_name}", gameArgsName},
                                          {"${version_name}", gameArgsVerName},
                                          {"${game_directory}", gameDir},
                                          {"${assets_root}", gameArgsAssetsDir},
                                          {"${assets_index_name}", gameArgsAssetsId},
                                          {"${auth_uuid}", gameArgsUuid},
                                          {"${auth_access_token}", gameArgsToken},
                                          {"${user_type}", gameArgsUserType},
                                          {"${version_type}", gameArgsVerType}});

將所有內容拼接後,可以使用std::system調用命令。如果在windows上拼接後的內容過長(8000於字符),你可能需要調用powershell來啓動。

更詳盡的內容可以參閱Neko Launcher


部分參閱來源:

  1. http://wiki.vg/Main_Page
  2. https://zh.minecraft.wiki/w/Tutorial:%E7%BC%96%E5%86%99%E5%90%AF%E5%8A%A8%E5%99%A8
👍 3

c++ modern c++ minecraft mc neko launcher

最后修改于28天前

评论

贴吧 狗头 原神 小黄脸
收起

贴吧

  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡

狗头

  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头

原神

  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神

小黄脸

  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  1. 流情 511天前

    太技术了。咋一看像是天书,仔细一看,这特么不是天书是神马

    1. boyarka 202天前

      It's very easy to find out any matter on web as compared
      to textbooks, as I found this ppiece of writing at this website.

目录

avatar

Hina

曇花一現

20

文章

98

评论

8

分类

初见

作業系統基礎 - 環境變數 & 環境變數的由來

287天前

OwO

33

網站正在更新中...
站點正在更新功能與樣式,如有樣式錯誤,請嘗試刷新緩存