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

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

  • Fabric 存于 fabric.mod.json。具体格式: fabric.mod.json
  • Forge 存于 META-INF/mods.toml,1.14 之前的老版本以 json 格式存于 mcmod.info,但很多老版本模组没有该文件。具体格式: mods.toml The 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 需要关注的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "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 需要关注的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[[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>]]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[[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 文件的代码。

点击查看
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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 设计