跳转到内容

ModLoader开发及修改方法

以下是关于如何修改ModLoader本体、如何将ModLoader本体以及插入到游戏中、如何将预装Mod嵌入到Html中的方法。若仅仅制作Mod,仅需按照以上方法打包Mod即可在Mod管理界面加载zip文件。


ModLoader开发及修改方法

本项目由于需要在SC2引擎启动前注入Mod,故对SC2引擎做了部分修改,添加了ModLoader的引导点,以便在引擎启动前完成Mod的各项注入工作。

修改过的 SC2 在此处:sugarcube-2 ,使用此ModLoader的游戏需要使用此版本的SC2引擎才能引导本ModLoader。

可在SC2游戏引擎项目中执行 node build.js -d -u -b 2 来编译SC2游戏引擎,编译结果在SC2游戏引擎项目的 build/twine2/sugarcube-2/format.js , 将其覆盖 [Degrees-of-Lewdity] 游戏的原版 devTools/tweego/storyFormats/sugarcube-2/format.js ,编译原版游戏本体获得带有ModLoader引导点的Html游戏文件, 随后按照下方的方法编译并注入此ModLoader到Html游戏文件,即可使用此ModLoader。


编译脚本

Terminal window
yarn run webpack:BeforeSC2
yarn run ts:ForSC2
yarn run webpack:insertTools

如何插入Mod加载器以及将预装Mod内嵌到html文件:

编写 modList.json 文件,格式如下: (样本可参见 src/insertTools/modList.json )

[
"mod1.zip",
"mod2.zip"
]

切换到 modList.json 所在文件夹

Terminal window
cd ./src/insertTools/modList.json
Terminal window
node "<insert2html.js 文件路径>" "<Degrees of Lewdity VERSION.html 文件路径>" "<modList.json 文件>" "<BeforeSC2.js 文件路径>"

例如:

Terminal window
node "H:\Code\sugarcube-2\ModLoader\dist-insertTools\insert2html.js" "H:\Code\degrees-of-lewdity\Degrees of Lewdity VERSION.html" "modList.json" "H:\Code\sugarcube-2\ModLoader\dist-BeforeSC2\BeforeSC2.js"

会在原始html文件同目录下生成一个同名的html.mod.html文件,例如:

Degrees of Lewdity VERSION.html.mod.html

打开Degrees of Lewdity VERSION.html.mod.html文件, play


附NodeJs及Yarn环境安装方法

  1. NodeJs 官网 下载NodeJs并安装,例如 node-v18.18.0-x64.msi
  2. 在命令行运行 corepack enable 来启用包管理器支持
  3. 结束

有关SC2注入点

ModLoader所需的唯一一个注入点是 sugarcube.js 文件中的jQuery启动处

/* eslint-enable no-unused-vars */
/*
Global `SugarCube` object. Allows scripts to detect if they're running in SugarCube by
testing for the object (e.g. `"SugarCube" in window`) and contains exported identifiers
for debugging purposes.
*/
window.SugarCube = {};
/*
Main function, entry point for the story.
*/
jQuery(() => {
'use strict';
const mainStart = () => {
// 原来的 `jQuery(() => {}) `的内容
};
// inject ModLoader on there
if (typeof window.modSC2DataManager !== 'undefined') {
window.modSC2DataManager.startInit()
.then(() => window.jsPreloader.startLoad())
.then(() => mainStart())
.catch(err => {
console.error(err);
});
}
else {
mainStart();
}
});

需要使用异步等待的方式,让原本的引擎启动逻辑等待ModLoader的初始化完毕,并等待ModLoader完成加载所有mod、执行mod注入脚本等待等的工作,之后才能执行原本的引擎启动逻辑。


技术说明

加载顺序

ModLoader会从4个地方加载mod

  1. html文件内嵌的【local】
  2. 远程web服务器【remote】 (如果是使用web服务器打开并且能读取到服务器上的modList.json)
  3. localStorage旁加载,上传文件(限制大小,所以现在没有使用)【localStorage】
  4. IndexDB旁加载,上传文件(现在用的)【IndexDB】

按照1234的顺序加载Mod,如果有同名Mod,后加载的会替代先加载的

有关 modList.json

这个 ModLoader 项目文件中的 modlist 只适用于预加载进html的mod【local】。 而对于远程加载的【remote】,modList.json 文件的格式与项目中的这个相同,唯一需要注意的是路径需要满足 fetch 的路径语法。远程加载的源码参见:

https://github.com/Lyoko-Jeremie/sugarcube-2-ModLoader/blob/dffd87657683b29ee663ac8279dbec8ce6611466/src/BeforeSC2/ModZipReader.ts#L698-L733

其中modFileZipPath

https://github.com/Lyoko-Jeremie/sugarcube-2-ModLoader/blob/dffd87657683b29ee663ac8279dbec8ce6611466/src/BeforeSC2/ModZipReader.ts#L714

就是在 modList.json 文件中列出的路径,RemoteLoader 会直接使用fetch访问服务器的这个路径来下载zip文件并加载mod。

举例:

[
"aaa.mod.zip", // 从html同一目录加载 aaa.mod.zip
"/rrr.mod.zip", // 从web服务器根目录加载 rrr.mod.zip
"./ddd/ccc.mod.zip", // 从html所在目录下的ddd文件夹中加载 ccc.mod.zip
"../../uuu.mod.zip", // 从html所在目录的父目录的父目录加载 uuu.mod.zip
"http://aaa.bbb.ccc/mmm.mod.zip" // 从网站 aaa.bbb.ccc 加载 mmm.mod.zip
]

有关 modList.json 的设计是这样的。

  • 对于需要打包自己的“整合包”的用户,可以直接在打包时指定一个自定义的 modList.json 来将想要的mod全部打包进html。【local】
  • 对于自己设立了web服务器的用户,可以在web服务器上的与html同一目录下放置一个modList.json文件,这样在启动时就会由RemoteLoader去加载这个modList.json文件中指定的mod。【remote】
  • 对于终端用户(玩家),在打开游戏后就会默认得到以上两个【local】+【remote】的mod,玩家自己可以通过mod管理器的界面再加载自己想要的mod。【IndexDB】

以上三个中,如果存在同名mod,则会存在覆盖关系,【remote】会覆盖【local】中的同名mod,而【IndexDB】会覆盖【local】+【remote】的同名mod。
也就是说,如果【local】和【remote】都存在mod A ,则最终生效的是【remote】的mod A。如果用户再自己加载一个mod A,则最终生效的是用户加载的这个mod A。

Mod、ModLoader、引擎、游戏 三者的结构

ModLoader和游戏的关系大约是 ((sc2引擎 + 游戏本体)[游戏] + (ModLoader + Mod)[Mod框架]) 这个结构

其中的 Mod框架 又细分为

(
(
ModLoader +
(
ModLoaderGui[Mod管理器界面] +
Addon[扩展插件]
)[预置Mod]
)[植入到html] + 其他Mod[上传或者远程加载]
)

要使用ModLoader玩游戏,需要使用经过修改的SC2引擎,例如 这里
其中最关键的部分就是上方的SC2注入点,ModLoader需要这个注入点才能实现在引擎启动前修改和注入Mod的工作

因为关系较为复杂,这里使用了GithubAction来实现自动编译

预编译版的修改版的SC2引擎 其中注入了ModLoader的引导点
预编译好的ModLoader以及Mod打包器(packModZip.js)和注入器(insert2html.js)和几个Addon
自动打包的包含ModLoader和Addon的原版DoL游戏

打包后的结构
((定制sc2引擎 + 原版游戏) + ModLoader)

因为ModLoader需要在sc2引擎启动之前把事情全部做完,但是引擎的启动事件是挂在jq的页面onLoaded事件上的,这个没法正常推迟, 所以最直接的解决办法就是把sc2引擎的启动代码单独放到一个函数里面,然后让ModLoader在jq的事件里面先启动,等启动完再调用原来的启动sc2引擎的代码

使得本来是 jq → sc2 的过程,变成 jq → ModLoader → SC2

如果需要手动打包,需要按照下面的步骤进行
  1. 构建 修改版的SC2引擎 获得 format.js ,
  2. 覆盖到游戏项目的 devTools\tweego\storyFormats\sugarcube-2\format.js ,再编译游戏就可以生成带修改版的SC2引擎的游戏
  3. 用ModLoader的注入器(insert2html.js)将ModLoader注入到游戏的html里。

以上的第三步在注入时会将 modList.json 中列出的Mod作为【Local】类型的Mod作为预置Mod一起注入到html中,默认情况下 modList.json 中会包含ModLoaderGui和一些Addon

有关Addon

由于将所有功能都实现在ModLoader中既不现实也不合理,特别是,有关特定游戏的功能更不应该实现在与SC2引擎绑定的ModLoader中,故ModLoader提供了Addon的功能。

Addon是一种特殊的Mod,作为一种功能扩展的形式存在,通过将常用功能集中在一个Mod中供其他ModLoader调用,这样的Mod就可以成为Addon。

例如在RimWorld中的 身体扩展战斗扩展发饰扩展 等等,这些Mod作为一种中间层,通过直接注入和修改游戏并扩展出新功能来供其他Mod调用,以此来方便其他mod的编写。

例如项目中的 ImageLoaderHook 就是一个Addon, 这个Mod通过挂钩DoL游戏中的图片加载器,实现了把Mod中的图片加载到游戏中的功能,游戏在加载图片时就会去读取Mod中的图片。

项目中的 CheckDoLCompressorDictionaries 是另一个不同功能的Addon, 这个Mod仅仅检查DoLCompressorDictionaries数据结构,并在发现数据结构变化后发出警示,来提示Mod开发者和使用者避免修改DoLCompressorDictionaries,以避免影响存档有效性。

项目中的 ReplacePatchTweeReplacer 则实现Passage和JS/CSS的字符串替换功能, 大部分需要修改游戏逻辑的Mod就不需要自己编写修改的代码,可以直接使用这两个Mod来实现字符串替换功能, 将这个功能独立出来,避免ModLoader过于臃肿的同时,也方便了对替换功能的快速更新和升级,在必要时Mod作者可以自行folk来实现更复杂的替换功能而不需要修改ModLoader的代码。