我的世界模组文件依赖关系

各种不同真麻烦……

注意到,无论 Forge、NeoForge 还是 Fabric,模组内部结构都比较规律,有个定义元数据的文本文件。

  • Fabric 存于 fabric.mod.json。具体格式: fabric.mod.json
  • Forge 存于 META-INF/mods.toml,1.14 之前的老版本以 json 格式存于 mcmod.info,但很多老版本模组没有该文件。具体格式: mods.tomlThe mcmod.info file
  • NeoForge 存于 META-INF/neoforge.mods.toml,目前兼容 Forge 的 META-INF/mods.toml。具体格式: neoforge.mods.toml

个别模组比较特殊,如 Kotlin for Forge,内部没有相关配置文件。一些老版本 Forge 模组的依赖信息在 META-INF/fml_cache_annotation.json

Fabric

fabric.mod.json 需要关注的内容:

{
  "id": "modid",    # 模组 ID
  "version": "1.0.0",
  "name": "Example mod",    # 模组的显示名称
  "environment": "*",   # * 双端、client 仅客户端、server 仅服务端

  "depends": {  # 模组的依赖模组/库
    "fabricloader": ">=0.16.10",
    "minecraft": "~1.21.5",
    "java": ">=21",
    "fabric-api": "*"
  }
}

NeoForge

neoforge.mods.toml 需要关注的内容:

[[mods]]    # 必填
modId="example" # 必填
version="1.0.0" # 必填
displayName="Example Mod"   # 必填

[[dependencies.example]]  # 可选
  modId="another"   # 必填
  type="required"   # 必填,依赖关系,不区分大小写
    # required(必需依赖),optional(可选依赖)
    # incompatible(不共存),discouraged(提示警告)
  versionRange="1.2.3"  # 必填
  ordering="NONE"   # 加载顺序
    # BEFORE:example 早于 another 加载
    # AFTER:example 晚于 another 加载
  side="BOTH" # BOTH、CLIENT、SERVER

Forge

mods.toml 中,模组属性绑定于表数组 [[mod]]。如果指定了依赖关系,则存于表数组 [[dependencies.<modid>]]

[[mods]]    # 必填
  modId="example"    # 必填
  version="1.2.0"   # 必填
  displayName="Example Mod" # 必填

[[dependencies.example]] # 可选
  modId = "minecraft"   # 必填
  mandatory = true  # 必填,true 必需,false 可选
  versionRange = "[1.20,)"  # 必填,空字符串表示任意
  ordering = "NONE"   # 加载顺序,NONE 表示无所谓
  side = "BOTH" # BOTH、CLIENT、SERVER

[[dependencies.example]]
  modId = "another"
  mandatory = true
  versionRange = "[1.20,)"
  ordering = "BEFORE"   # example 早于 another 加载。AFTER 表示晚于

而在 1.14 之前的旧版本中,如果作者写代码时注解 @Mod 的属性 useMetadatatrue,则加载 mcmod.info 中的设置。如果 mcmod.infouseDependencyInformationtrue,则以下三项生效:

  • 数组 requiredMods:前置模组 modid 的列表。
  • 数组 dependencies:在当前模组之前加载的 modid 列表
  • 数组 dependants:在当前模组之后加载的 modid 列表。

DIY 模组依赖检查代码

现在只写了检查 NeoForge 和 Forge 模组中 toml 文件的代码。

点击查看
import pathlib
import zipfile

import tomllib


class Mod:
    def __init__(self, file_name, name, mod_id) -> None:
        self.file_name = file_name
        self.name = name
        self.mod_id = mod_id
        self.前置 = {}
        self.可选前置 = {}
        self.被需要 = {}
        self.被可选需要 = {}

    def add_dependency(self, mod_id, side):
        self.前置[mod_id] = side

    def add_optional_dependency(self, mod_id, side):
        self.可选前置[mod_id] = side

    def add_required_by(self, mod_id, side):
        self.被需要[mod_id] = side

    def add_optional_required_by(self, mod_id, side):
        self.被可选需要[mod_id] = side


OUTPUT_LINES: list[str] = []
MODS: dict[str, Mod] = {}


def add_line(line: str):
    OUTPUT_LINES.append(line + "\n")


def output(path="./mods.txt"):
    file = pathlib.Path(path)
    if file.suffix == ".txt":
        with file.open("w") as f:
            f.writelines(OUTPUT_LINES)
    else:
        print("文件路径不正确")


def scan_mods_dir(dir: pathlib.Path):
    for jar in dir.iterdir():
        if jar.suffix == ".jar":
            scan_jar(jar)


def scan_jar(jar: pathlib.Path):
    dir = zipfile.Path(jar, "META-INF/")
    results = [f for f in dir.glob("*mods.toml")]
    if len(results) < 1:
        add_line(f"{jar.name} 中未找到 mods.toml 文件")
    else:
        mod_file = results[0]
        with mod_file.open("rb") as f:
            content = tomllib.load(f)
        mod_id = content["mods"][0]["modId"]
        display_name = content["mods"][0]["displayName"]
        mod = Mod(jar.name, display_name, mod_id)
        if "dependencies" in content:
            pick_dependencies(mod, content)
        if mod.mod_id in MODS:
            add_line(f"模组 ID 重合:{MODS[mod.mod_id]}{mod.file_name}")
        else:
            MODS[mod.mod_id] = mod


def pick_dependencies(mod: Mod, content):
    for each_mod_id, each_mod in content["dependencies"].items():
        if each_mod_id != mod.mod_id:
            add_line(f"{mod.file_name} 添加了 {each_mod_id} 的依赖关系")

        for each_item in each_mod:
            if "type" in each_item:
                need_type = each_item["type"]
            else:
                mandatory = each_item["mandatory"]
                if mandatory is True:
                    need_type = "required"
                else:
                    need_type = "optional"

            if each_item["modId"] not in ["minecraft", "forge", "neoforge"]:
                if "side" in each_item:
                    if each_item["side"] == "BOTH":
                        side = "双端"
                    elif each_item["side"] == "SERVER":
                        side = "服务端"
                    else:  # "CLIENT"
                        side = "客户端"
                else:
                    side = "没说放哪"
                if need_type == "required":
                    mod.add_dependency(each_item["modId"], side)
                elif need_type == "optional":
                    mod.add_optional_dependency(each_item["modId"], side)


def seek_required():
    for mod_id, mod in MODS.items():
        for tmp_mod_id, side in mod.前置.items():
            if tmp_mod_id in MODS:
                MODS[tmp_mod_id].add_required_by(mod_id, side)
            else:
                add_line(f"* 没找到 {mod.file_name} 需要的 {tmp_mod_id},或许已内置")
        for tmp_mod_id, side in mod.可选前置.items():
            if tmp_mod_id in MODS:
                MODS[tmp_mod_id].add_optional_required_by(mod_id, side)


def analysis_relations():
    for mod in MODS.values():
        add_line(f"{mod.name}")
        add_line(f"\t{mod.file_name}")
        if len(mod.前置) > 0:
            add_line("前置:")
            for tmp_mod_id, side in mod.前置.items():
                if tmp_mod_id not in MODS:
                    add_line(f"\t* 需要 {tmp_mod_id},但没有文件")
                else:
                    add_line(f"\t{side}{MODS[tmp_mod_id].name}")
                    add_line(f"\t\t{MODS[tmp_mod_id].file_name}")
        elif len(mod.可选前置) > 0:
            add_line("可选前置:")
            for tmp_mod_id, side in mod.可选前置.items():
                if tmp_mod_id in MODS:
                    add_line(f"\t{side}{MODS[tmp_mod_id].name}")
                    add_line(f"\t\t{MODS[tmp_mod_id].file_name}")
                else:
                    add_line(f"\t{side}(未添加):{tmp_mod_id}")
        elif len(mod.被需要) > 0:
            add_line("需要本模组:")
            for tmp_mod_id, side in mod.被需要.items():
                add_line(f"\t{side}{MODS[tmp_mod_id].name}")
                add_line(f"\t\t{MODS[tmp_mod_id].file_name}")
        elif len(mod.被可选需要) > 0:
            add_line("可选需要本模组:")
            for tmp_mod_id, side in mod.被可选需要.items():
                add_line(f"\t{side}{MODS[tmp_mod_id].name}")
                add_line(f"\t\t{MODS[tmp_mod_id].file_name}")
        else:
            add_line("独立模组")
        add_line("\n")


mods_dir = input()
mods_dir = pathlib.Path(mods_dir)
scan_mods_dir(mods_dir)
seek_required()
add_line("<---------------->\n")
analysis_relations()
output()
当你有机会做出选择时,不要让自己后悔
使用 Hugo 构建
主题 StackJimmy 设计