本页将教会大家如何查询并且拼接JVM和Arguments默认启动参数

首先,在Minecraft的版本中,分为这两类启动版本:

  1. ≤1.12.2
  2. ≥1.13

这两种启动版本的Json文件很不一样,同时,还有一个地方要注意:

  • ≤1.12.2——LiteLoader

我将会直接将其统一放进同一个函数里,因为只需要判定某一个参数即可。所以几乎无需多余的判定。

首先我们要点进Minecraft的1.19.4的版本json文件进行查看。我们可以看到内容大致如下:

{
  "arguments": {
    "game": [ //有rule的参数为额外参数,直接拼接在game列表中的则是默认参数
      "--username",
      "${auth_player_name}",
      ... //其为默认游戏参数【game默认参数在开头】
      { 
        "rules": [
          {
            "action": "allow",
            "features": {
              "is_demo_user": true
            }
          }
        ],
        "value": "--demo"
      },
      ... //其为额外游戏参数
    ],
    "jvm": [
      { //jvm的额外参数在顶上,默认参数在下面。
        /*
          有部分JVM参数包含rules键值,我们在拼接启动参数的时候需要考虑这种情况:
          1.rules是一个Json列表的形式
          2.如果rules里面有一个值的action为disallow,则在这以下的os键值对里的name所指代的操作系统无需拼接此参数。反之亦然。
          3.有些rules键值里面的value是个数组类型,我们在实际情况下也应该考虑这种情况。
        */
        "rules": [
          {
            "action": "allow",
            "os": {
              "name": "osx"
            }
          }
        ],
        "value": [
          "-XstartOnFirstThread"
        ]
      },
      ... //其为额外jvm参数,其中由os键中的osx代表的是Macos的参数,linux则是Linux的参数,windows则是Windows参数,arch: x86则代表了32位系统。
      // 然后,version键值只在Windows出现,而且:【^10\】代表了其需要Windows 10版本以上才需要添加。
      "-Djava.library.path=${natives_directory}",
      ... //其为默认jvm参数
    ]
  },
  "assetIndex": {
    "id": "3",
    "sha1": "0e432ccd3ef65853034193811d92ac47d0b7ca5d",
    "size": 409894,
    "totalSize": 555638139, 
    "url": "https://piston-meta.mojang.com/v1/packages/0e432ccd3ef65853034193811d92ac47d0b7ca5d/3.json"
  }, //其为Minecraft所需要的资源索引json文件。
  "assets": "3",
  "complianceLevel": 1,
  "downloads": {
    "client": {
      "sha1": "958928a560c9167687bea0cefeb7375da1e552a8",
      "size": 23476620,
      "url": "https://piston-data.mojang.com/v1/objects/958928a560c9167687bea0cefeb7375da1e552a8/client.jar"
    }, 
    ... //其为Minecraft所需要的原版jar文件和原版mapping映射表下载地址。
  },
  "id": "1.19.4", //其为Minecraft的游戏id【一般随着Minecraft的版本命名而区分。】
  "javaVersion": {
    "component": "java-runtime-gamma",
    "majorVersion": 17
  }, //其为Minecraft所需要的Java运行时环境,component为MC官方提供的下载源中需要的环境依赖。【这个后期说道自动下载Java时会说道。】
  "libraries": [
    {
      "downloads": {
        "artifact": {
          "path": "ca/weblite/java-objc-bridge/1.1/java-objc-bridge-1.1.jar",
          "sha1": "1227f9e0666314f9de41477e3ec277e542ed7f7b",
          "size": 1330045,
          "url": "https://libraries.minecraft.net/ca/weblite/java-objc-bridge/1.1/java-objc-bridge-1.1.jar"
        }
      },
      ... //其为Minecraft所需的所有类库文件,其中包含了资源名称,downloads键下还有artifact键,里面有资源保存路径、资源sha1、资源大小、资源下载网址等。
      /*
        有部分类库文件包含rules键值,我们在拼接启动参数的时候需要考虑这种情况:
        1.rules是一个Json列表的形式
        2.如果rule里面有一个值的action为disallow,则在这以下的os键值对里的name所指代的操作系统无需拼接此参数。反之亦然。
      */
    }
  ],
  "logging": {
    "client": {
      "argument": "-Dlog4j.configurationFile=${path}",
      "file": {
        "id": "client-1.12.xml",
        "sha1": "bd65e7d2e3c237be76cfbef4c2405033d7f91521",
        "size": 888,
        "url": "https://piston-data.mojang.com/v1/objects/bd65e7d2e3c237be76cfbef4c2405033d7f91521/client-1.12.xml"
      },
      "type": "log4j2-xml"
    } //Minecraft Log4J配置文件路径,这个一般时用于记录玩家客户端执行的,一般我们不需要将其包含进启动参数内,因此这一整个logging键值没用。
  },
  "mainClass": "net.minecraft.client.main.Main", //Minecraft主类【需要添加进启动参数】
  "minimumLauncherVersion": 21, //Minecraft最小启动器版本【一般指的是官启版本(猜的】
  "releaseTime": "2023-03-14T12:56:18+00:00", //发布时间
  "time": "2023-03-14T12:56:18+00:00", //与上面相同
  "type": "release" //发布类型【如果是snapshot,就会显示snapshot,但1.19.4是release发布版。】
}

以上是我们的Minecraft原版Json文件,这个文件如何获取的晚点我会和大家娓娓道来,大家只需要先学习我们如何根据这个Json文件进行启动游戏的就好了!

在使用Delphi自带的JSON库时,大家需要在自己的头文件中引用一个单元文件。键入以下单元即可:

uses
  JSON;

然后,首先我们要写一个函数,这个函数的名字就叫做:【selectParam】

我们将这个函数放进我们的自制类里面。可以选择放在private,也可以选择放在public,那我这里就选择放在public里了吧!

type
  TForm1 = class(TForm)
    Button1: TButton;
    ComboBox1: TComboBox;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
  Launcher = class
  private
  public
    function SelectParam(json: TJsonObject): string;
  end;

在Delphi里面,函数的意思是有返回值的方法,使用function定义,而过程是没有返回值的方法,用procedure定义。我们上面定义的是function,因此这是个函数。

然后,我们的参数列表中填入的是一个TJsonObject类型的,意味着我们这个参数是一个Json对象。我们需要将字符串转换成Json对象才能传参。

紧接着,我们就要开始实现这个函数了。我们在implementation下方依旧如同上方一样实现上述Launcher类里的SelectParam函数。请看示例

function Launcher.SelectParam(json: TJsonObject): string;
begin //开头的函数得与你在类中定义的函数一致。  
  result := ''; //此处设置该函数返回值默认值为空值。
  var param := '-XX:+UseG1GC ' + //每一个参数末尾都需要加上空格。
  '-XX:-UseAdaptiveSizePolicy ' +
  '-XX:-OmitStackTraceInFastThrow ' +
  '-Dfml.ignoreInvalidMinecraftCertificates=True ' +
  '-Dfml.ignorePatchDiscrepancies=True ' +
  '-Dlog4j2.formatMsgNoLookups=true '; //这些都是我们需要的默认JVM参数。因为PCL2添加了这些参数,仅此而已啦!
  // param := param + additionJVM; 
  //该处,如果你想为Minecraft手动添加额外JVM参数,可以在这里写。同时参数也可以多添加一个additionJVM,类型String来添加。也可以添加全局变量。我就不添加了,我不太想。
  param := param + '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump '; //由于我们这个启动器面向的是Windows,自然,对于Windows的这个额外JVM参数,我们需要将其当作默认JVM参数添加进入。
  if Win32MajorVersion = 10 then param := param + '"-Dos.name=Windows 10" -Dos.version=10.0 ' ;
  //该处使用了Delphi内置函数Win32MajorVersion来判断电脑的操作系统版本,如果是Windows10,则拼接下面两个参数,反之不拼接。
  var judge: boolean; //在外部定义一个judge判断布尔变量
  var argu: TJsonObject; //在外部定义一个argu的Json对象变量。
  try
    judge := false; //给外部定义的judge变量初值设为false。
    argu := json.GetValue('arguments') as TJsonObject; //通过传入的Json参数获取里面的arguments键,然后将其转换成Json对象形式。传到外面。
    var jvm := argu.GetValue('jvm') as TJsonArray; //再通过arguments对象进入jvm键值对里面,将其转换成Json数组。
    if jvm.Count = 0 then raise Exception.Create('No jvm arguments'); //如果jvm列表内元素为空,则抛出报错,执行except语句,将judge返回true。
    for var i in jvm do begin //使用循环遍历jvm参数
      if i.ToString.IndexOf('rules') <> -1 then continue; //一旦遇到了rules键值,则判定此为额外JVM参数,故暂不添加进param中。
      param := param + i.Value.Trim + ' '; //末尾需要加上空格。并且需要去除掉在默认参数中的空格。
    end;
  except
    judge := true; //如果找不到arguments键,或者arguments键中的jvm值不存在,或者为空,则抛出报错。
  end; 
  if judge then begin //如果judge为true,意味着上面jvm参数拼接失败,则执行。
    param := param + ' -Djava.library.path=${natives_directory} -cp ${classpath} ';
    judge := false;
  end; //上述代码为Minecraft主动添加了JVM参数,此处一般是用于判断版本是否小于1.12.2。
  param := param + '${authlib_injector_param}'; //这里需要添加一个Authlib-Injector,这个我们晚点再说!晚点到登录到Authlib-Injector时再说吧!
  try
    param := param + '-Xmn256m -Xmx' + MaxMemory + 'm ' + json.GetValue('mainClass').Value + ' '; 
    //上述代码给MC启动参数总添加了几行默认游戏参数,其中包括了最小内存、最大内存、主类。且如果无法从json文件中找到mainClass键,则抛出报错。错误见下。
  except
    messagebox(0, '无法拼接mainClass主类参数,参数拼接失败。', '无法拼接参数', MB_ICONERROR); //此处显示一个信息框,无法拼接主类参数。
    exit; //退出该函数。
  end;
  try //该try语句是适配1.13版本以上的版本。
    var game := argu.GetValue('game') as TJsonArray; //进入了arguments后,我们再接着通过这个arguments对象进入game键值对里面,然后将其转换成Json数组形式。
    if game.Count = 0 then raise Exception.Create('No game arguments');
    for var i in game do begin //使用相同的手段将game参数拼接到jvm参数的后面。
      if i.ToString.IndexOf('rules') <> -1 then continue;
      param := param + i.Value.Trim + ' '; 
      //末尾需要加上空格。并且需要去除掉在默认参数中的空格。目的请看Fabric的版本Json文件。其中有一个-DFabricMcEmu键,后面跟了空格,因此这里需要对此进行空格清除。
    end;
  except
  end;
  try //在这里实则判断param中是否有minecraftArguments值,以适配1.12.2以下版本
    var mcargu := json.GetValue('minecraftArguments').Value; //开始判断是否有
    if mcargu = '' then raise Exception.Create('No 1.12.2 Arguments'); 
    //如果没有这个键,或者这个键为空,则抛出报错,自动执行except语句,然后except语句内啥也没有,就接着往下执行。
    param := param + mcargu; //此处如果有则拼接这个参数。
    //json中的键值对'minecraftArguments'就是在1.12.2以下版本所用的启动参数。这一串代码实则判断假如MC版本小于等于1.12.2,则主动拼接这个参数。
  except //此处不用设置judge了。
  end;
  if judge then begin //此处为当未能拼接任意默认游戏参数时抛出的报错。
    messagebox(0, '无法拼接默认游戏参数,参数拼接失败。', '无法拼接参数', MB_ICONERROR); //这里置一个信息框。
    exit; //退出该函数
  end;
  result := param; //将参数当作返回值返回。
end;

我相信,这些注释足以使玩家明白其中的道理。如果大家觉得用电子书没有换行看着太麻烦,大家可以将其复制进记事本中慢慢观看哦!

噢,好像发现了一件事!我通过查看PCL2的源码时,发现了一个非常神奇的一幕:请看截图: PCL2启动参数

我们在这个截图里,发现了其实在拼接Optifine Forge和仅安装了Optifine的时候,参数里可能会有两个或以上的--tweakClass作为启动参数,然后Optifine可能不会是最后一个,因此我们需要手动将该参数放到最后,这样才不会启动失败!

我们首先来尝试一下:

  ...//前面的代码:我们直接在我们上面的result := param的上面一句直接写:
  if param.Contains('--tweakClass optifine.OptiFineTweaker') or param.Contains('--tweakClass optifine.OptiFineForgeTweaker') then begin
    param := param.Replace('--tweakClass optifine.OptiFineTweaker', '').Replace('--tweakClass optifine.OptiFineForgeTweaker', '') + '--tweakClass optifine.OptifineForgeTweaker';
  end;
  result := param;

简单来说,上面的函数我们一句参数,我们将optifine的tweakClass放到最后了!就是首先replace掉所有的--tweakClass optifine的,然后将正确的参数拼接在后面。

我们上述的代码中,其实已经将所有的版本都支持进去了,包括forge、Fabric、Quilt、LiteLoader等都可以正常的拼接启动参数了。包括MC的从远古版本到最新版本的启动参数支持都在这一个函数里哦!

大家只需要看个逻辑就好,完全可以翻译成自己的编程语言进行开发哦!现在大家能领略Delphi的魅力了吗?与C#、Java等语言是否觉得比较相同呢?