字符串模板,顾名思义就是json中双引号内的${template}。

一般情况下,美元符号右边一个大括号内的内容,如果在同一个字符串内出现多次,那多半是同一个意思。我们首先请看原版的字符串模板吧!ps:还是以原版1.19.4作为例子,下面我将介绍我已经拼接好的。

<17.0.6的Java> -XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Dos.name=Windows 10" -Dos.version=10.0 -Djava.library.path=${natives_directory} -Dminecraft.launcher.brand=${launcher_name} -Dminecraft.launcher.version=${launcher_version} -cp ${classpath} --username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --clientId ${clientid} --xuid ${auth_xuid} --userType ${user_type} --versionType ${version_type}

哝,总的启动参数也就只有这么一点点,我们只是查找了其中的cp后面的classpath键值与替换了inheritsFrom键后面的东西吧!

如果大家觉得翻页太麻烦的话,可以将其复制到记事本里面,然后再观看哦!

好了,下面我们就来介绍一下如何使用上期所指认的所有方法吧!

首先,我们双击窗体按钮,进入Button1Click事件,这个事件的意思代表着按钮1的点击事件,然后我们就需要在里面写东西了

我们需要查看该版本是否是forge、fabric、quilt等模组加载器的版本,我们就需要判断其是否有inheritsFrom键。

开始写代码: 首先,在单元文件的implementation的上方,加上这样一句话:

var
  Form1: TForm1;
  lch: Launcher;

这样,我们就可以直接通过lch来调用Launcher中的所有内置代码了!

procedure TForm1.Button1Click(Sender: string);
begin //还是如此,我这里用双右划线代替。大家在使用Delphi实际操作时,必须用单右划线。
  var spath := Concat(MinecraftPath, '\\versions\\', ComboBox1.Items[ComboBox1.ItemIndex]);
  //上述代码是获取当前你从下拉框中选择的版本文件夹。众所周知,在下面的所有用到的方法中,我们都使用了lch去获取类里面的函数。
  var yjson := GetOutsideDocument(lch.GetRealPath(spath, '.json')); //获取当前文件夹下的json文件内容。
  var gjson := GetOutsideDocument(lch.GetRealPath(lch.GetInheritsFrom(spath, 'inheritsFrom'), '.json')); //获取是否含有inheritsFrom键文件内容。
  var rjson := lch.ReplaceInheritsFrom(yjson, gjson); //当然,如果你觉得换成三行太麻烦了,你完全可以将上面两个变量直接填在这里面,但是我不太建议这么做。
  //由于在前期工作我们已经做得相当完善了,因此,我们只需要用这么几个函数,即可将最正确的json文件给加载到内存之中给我们调用了。
  var root := TJsonObject.ParseJSONValue(rjson) as TJsonObject; //将上述获取好的json转换成json对象形式。
  var res := lch.SelectParam(root); //成功获取到拼接完成后的json文件。
  //在这里,我们需要开始判断所有需要替换的字符串模板有哪些,其中包括了Vanilla、Forge、Fabric、Quilt的版本。
  res := Concat(res, ' --width ', wt, ' --height ', ht); //新增最后两个参数,分别代表了长宽高。这里我一般会输入1000和800。
  res := res //开始进行字符串替换。
  .Replace('${auth_player_name}', UserName) //这里开始判断的是玩家名称【由于目前我们做的只有离线登录,正版登录的玩家名称后期再说了。】
  .Replace('${version_name}', root.GetValue('id').Value) //这里开始判断的事玩家版本【其实随便填什么都可以啦!但是这里以json中的id值作为判断依据。】
  .Replace('${game_directory}', Concat('"', MinecraftPath, '"')) //重点!敲黑板!此处应该填入的是一个路径,用于判断版本隔离的,
  //大家可能会发现,BakaXL的该键值,总是指向<source>\versions\<versionname>下的目录。而PCL2,则会让各位选择版本隔离。【这点我们以后说,大家只需要做个了解即可。】
  .Replace('${assets_root}', Concat('"', MinecraftPath, '\\assets', '"')) //assets_root一般指的是MC启动所需要的assets路径。这里依旧使用了两个右划号。
  .Replace('${assets_index_name}', Root.GetValue('assets').Value) //其实,这里可以使用assetIndex->id代替的,也可以直接用assets。这个键值是原版就有的。然后assetIndex这个键后期我们说道下载的时候再介绍。
  .Replace('${auth_uuid}', '0123456789abcdef0123456789abcdef') //这里需要填入一个无符号的32位uuid键作为玩家的唯一标识符。大家可以用Delphi内置函数随机生成一个UUID,但是这里建议每次进入游戏的UUID键值必须一致,否则可能会导致物品栏内的物品失踪的bug。
  .Replace('${auth_access_token}', 'none') //这里填入你的access_token,想必大家应该看到过MCBBS的那个帖子了吧,这个作为玩家的正版登录令牌,在离线模式下无需此键【填入任何字符即可】,但是在正版登录中,你绝对不允许将带有access_token的启动参数发给对方。
  .Replace('${user_type}', 'Legacy') //用户类型,一般如果是正版登录,则替换为Mojang、外置同理,如果是离线登录,则替换为Legacy。
  .Replace('${version_type}', VersionType) //填入版本类型,一般是显示在游戏启动后的左下角以及游戏内按下F3后左上角显示的。
  .Replace('${natives_directory}', Concat('"', lch.GetRealDirectory(spath, 'natives'), '"')) //这个路径填入Minecraft本地库路径,不过照常来说,官启的本地库路径一般在<C:\Users\<用户名>\AppData\Local\Temp>中,
  //而HMCL、PCL、BakaXL所存放的本地库路径,则在<MC路径>\versions\<versionName>中。因此,我们需要适配这种情况。这里使用了一个自定义函数GetRealDirectory,我们稍后说。
  .Replace('${launcher_name}', 'CourseLauncher') //定义你的启动器名称【暂不知其用途】
  .Replace('${launcher_version}', '1.0') //定义你的启动器版本【暂不知其用途】
  .Replace('${classpath}', Concat('"', lch.GetCPLibraries(root, MinecraftPath, spath), '"')) //使用GetCPLibraries键替换掉classpath键。
  .Replace('${library_directory}', Concat('"', MinecraftPath, '\libraries', '"')) //这里可以直接填入libraries的路径。
  .Replace('${classpath_separator}', ';')//这里是每个classpath的分开符号。
  .Replace('${authlib_injector_param}', ''); //这里需要将Authlib-Injector暂时去掉,这个还是老样子,我们后期再说! 
  //替换到这种程度就可以了。
end;

最后,我们在上面写了一个GetRealDirectory的函数,这个函数我们也要去实现一下哦!

function Launcher.GetRealDirectory(path, suffix: string): String;
var // 同上
  Dirs: TArray<String>;
begin
  result := '';
  if DirectoryExists(path) then // 判断文件夹是否存在
  begin
    Dirs := TDirectory.GetDirectories(path); // 获取文件
    for var I in Dirs do begin // 循环判断
      if I.IndexOf(suffix) <> -1 then begin// 判断是否在里面
        result := I;
        exit;
      end;
    end;
  end;
end;

然后,我们就敲完所有的代码了!

我们可以在最后一个Replace的后面,建立一个messagebox用来查看我们的参数是否拼接正确哦,记住,我们所有的参数都只能在一行中实现,不允许出现分段或者多行哦!

我们在最后一步,在Replace完了之后,我们只需要再添加一个函数,用于启动我们的MC即可!

  ShellExecute(Application.Handle, 'open', pchar(JavaPath), pchar(res), nil, SW_SHOWNORMAL);
  //记住,这里的中间两个参数为pchar类型,意味着这是一个char指针类型。

只需要简简单单的一行,我们即可通过我们定义的Java启动MC了!这是一个Delphi的内置函数,用于执行cmd命令时用的。在这里面,我们也是首次使用到了我们的JavaPath哦!在使用这个函数的时候,我们需要引用一个单元文件:

uses
  ShellAPI;

关于这个函数的使用教程,请参见:网址

具体功能见下:

参数功能
1默认均为Application.Handle
2执行外部程序时填入open,打开网址时填入nil
3外部程序入口,如果是网址则填入网址
4程序参数,如果没有可以填nil
5默认文件夹,直接填nil
6显示方式,这里只需了解两个,1.SW_SHOWNORMAL,正常显示,2.SW_HIDE,隐藏窗口显示。

好了,接下来,我们便可以正常的启动我们的MC了!你应该能看到,【假设你的MC已经被别的启动器下载并且被启动过至少一次。】【同样的,不仅如此,我们甚至还早已可以启动1.12.2以下以及远古版本了!】

我们造的轮子可真多,难道不是么?

哦,对了,此时此刻,你需要对你的MC版本进行Java版本判断,例如1.12.2的Forge只能使用java8启动、1.17以上版本只能使用Java17启动等。