ChunkAPI

[!INFO] Git Commit: d7b4afe | Updated: 2026-05-17

ChunkAPI(modid chunkapi)不是一个内容 mod,而是一个区块数据序列化库 + core mod。它不注册任何方块、物品、实体、维度、群系或结构,玩家在游戏里看不到它。它的全部作用是:

  1. 提供一套 API,让其他 mod 把自己的一小段数据挂进区块(存盘、读盘、区块克隆、网络同步四件事统一由它分发);
  2. 用 core mod + 10 个 mixin + 4 条 Access Transformer 把原版区块读写与封包代码的挂钩点接管掉;
  3. 把原版自己的区块字段(Biomes、Blocks、Add、Data、SkyLight、BlockLight、HeightMap、LightPopulated)重新实现成 6 个内建数据管理器,从而对区块数据做统一的分片打包。

mcmod.info 里唯一的描述是仓库 README 的第一句:“Chunk data, simplified.”

⚠️ 这个 mod 不是什么(对任务简报的核实)

任务简报把它描述为"被许多世界生成 mod 使用的区块/世界操作库",并预设了"世界生成钩子、人口生成事件、方块替换/扫描、结构/特征放置、群系、配置项、事件类型"等维度。源码核实后,这些绝大部分不存在:

  • 本 mod 不做任何世界生成或人口生成。IWorldGenerator、WorldGen、populate(、PopulateChunk、addGenerator、MapGenStructure、Feature、BiomeProvider、WorldProvider、registerDimension 在 src/ 下全部零命中。它是读写已有区块,不是生成区块。
  • BiomeManager 不是群系。它只是把原版 Chunk.getBiomeArray() 的 256 字节搬进打包框架,不定义任何群系。因此本 wiki 没有 biome/ 目录。
  • 没有方块、物品、实体、TileEntity、多方块结构、附魔、Buff/Debuff、成就、维度、热键、指令、事件。
  • 不写任何配置文件。
  • 不调用矿词典、不注册配方、无任何液体/能量接口。

完整的 grep 证据见页面底部"本 mod 没有的东西"。

基本信息

属性 值
仓库目录 ChunkAPI(FalsePattern 原始,LegacyModdingMC 维护的 GTNH fork)
modid chunkapi
显示名 ChunkAPI
@Mod 类 com.falsepattern.chunk.internal.ChunkAPI(internal 包,不是根包!)
Minecraft 1.7.10(acceptedMinecraftVersions = "[1.7.10]")
源码规模 47 个 .java 文件(其中 7 个是 package-info.java)
根包 com.falsepattern.chunk(group = "com.falsepattern")
作者 FalsePattern(mcmod.info 的 authorList),图标 Houstonruss
许可 LGPLv3 + api 包的"additional permissions"(见 随包资源清单)
core mod coreModClass = "internal.core.CoreLoadingPlugin"
AT accessTransformerFile = "chunkapi_at.cfg"(4 条规则)
mixin mixin.pkg = "internal.mixin.mixins",10 个类,框架 com.gtnewhorizon.gtnhmixins
API 包 api.packages = listOf("api")
内容维度 仅 setting(9 个条目)
配置文件 无(不生成任何 .cfg)
构建 com.falsepattern.fpgradle-mc 2.1.0(Kotlin DSL);gradle.properties 仅 2 行,无 mod 侧配置键
发布 CurseForge 844484 / Modrinth y0vBUOla / mvn.falsepattern.com

⚠️ @Mod 注解类在 internal 包里:com.falsepattern.chunk.internal.ChunkAPI。类名与 mod 名同名,但从源码树位置看不出 modid——modid 实际定义在 build.gradle.kts 的 mod { modid = "chunkapi" },@Mod 里引用的是构建期生成的 Tags.MOD_ID。

⚠️ gradle.properties 里没有 coreModClass / usesMixins / accessTransformersFile / mixinPlugin——这些键全部在 build.gradle.kts 的 minecraft_fp { } 块内,用 Kotlin DSL 写法(core { coreModClass = ...; accessTransformerFile = ... }、mixin { pkg = ... })。gradle.properties 全文只有两行:

org.gradle.caching=true
org.gradle.configuration-cache=true

全部 9 个条目

机制与设定(9 个)

条目 内容
API 接口 核心:DataManager 根接口 + 6 个嵌套接口(PacketDataManager / CubicPacketDataManager / BlockPacketDataManager / StorageDataManager / ChunkDataManager / SubChunkDataManager)的完整方法表;DataRegistry 门面;OrderedManager 排序
数据注册表 DataRegistryImpl(558 行)的注册/排序/停用规则;ordering 约定;maxPacketSize 累加公式;特权 NBT 命名空间;未知 manager 的降级处理
内置原版数据管理器 6 个内建 manager(blockid/metadata/skylight/blocklight/lighting/biome)各自实现的接口、NBT 键、包体积;基类 VanillaManager 与 NibbleManager
序列化流水线 6 条独立路径:存盘、读档、区块包 S→C、单方块包、多方块包、level.dat;每步的调用顺序与 verbatim 注入点
核心 mod 与读档闸门 CoreLoadingPlugin(IFMLLoadingPlugin + IEarlyMixinLoader)与 ChunkAPICoreModContainer(WorldAccessContainer);读档兼容闸门:manager 增删/版本变更 → 弹窗确认 → 自动世界备份
Mixin 清单 10 个 mixin 类的 @Mixin 目标、手法、verbatim 注入点与 SRG 别名;Mixin 枚举 5 个分组;VanillaCore/ThermosCore 互斥;Thermos 与 Spool 条件
Access Transformer META-INF/chunkapi_at.cfg 的 4 条规则逐条说明;为何 getAccessTransformerClass() 返回 null 却仍然生效
数组工具类 ArrayUtil 的 11 个 copyArray 重载与统一的 null/长度契约;NibbleArray 特例为何依赖 AT
随包资源清单 src/main/resources 下 8 个文件逐个列出(含字节数);mcmod.info 全文;两份内容相同的 mixin JSON

API 表面总览

对外 API 只有 4 个类型,全部在 com.falsepattern.chunk.api 包:

类型 种类 消费者做什么
DataManager 接口(含 6 个嵌套接口) 声明"我的数据要存/读/发/收/克隆"
DataRegistry @ApiStatus.NonExtendable 静态门面 注册、停用、查询、广播克隆
OrderedManager 普通类(Comparable) 排序键载体
ArrayUtil @ApiStatus.NonExtendable 工具类 11 个重载的原地数组复制

DataManager 的 6 个嵌套接口构成"一个类可以同时是多种身份"的设计,注册表用 instanceof 逐个分流:

接口 父接口 粒度 内建实现者
PacketDataManager DataManager 整区块(随区块包) BlockIDManager、MetadataManager、BlocklightManager、SkylightManager、BiomeManager(5 个)
CubicPacketDataManager DataManager 单 cube(仅 CubicChunks,@ApiStatus.Experimental) BlockIDManager、MetadataManager、BlocklightManager、SkylightManager(4 个)
BlockPacketDataManager DataManager 单方块(随 S22/S23 包) BlockIDManager、MetadataManager(2 个)
StorageDataManager DataManager 版本与提示文案 全部 6 个
ChunkDataManager StorageDataManager 每区块 1 次 LightingManager、BiomeManager(2 个)
SubChunkDataManager StorageDataManager 每区块 16 次 BlockIDManager、MetadataManager、BlocklightManager、SkylightManager(4 个)

⚠️ PacketDataManager 的 5 个内建实现者中有 4 个是"隐式"的——MetadataManager / BlocklightManager / SkylightManager 的源码里没写 implements PacketDataManager,它们从 NibbleManager 父类继承了这个身份。LightingManager 则是唯一不参与任何网络同步的内建 manager。

6 条序列化路径

# 路径 服务端挂钩点 客户端挂钩点
1 存盘 AnvilChunkLoader.writeChunkToNBT @Inject(HEAD, cancellable) —
2 读档 AnvilChunkLoader.readChunkFromNBT @Inject(HEAD) + CallbackInfoReturnable —
3 区块包 S→C S21PacketChunkData.func_149269_a @Overwrite + func_149275_c() @Overwrite Chunk.fillChunk @Overwrite(@SideOnly(CLIENT))
4 单方块包 S23PacketBlockChange.<init> @Inject(RETURN) 或 chunkapi$init NetHandlerPlayClient.handleBlockChange @Inject(RETURN)
5 多方块包 PlayerInstance$PlayerInstance.sendChunkUpdate @Redirect(NEW) → 拆成 S23PacketBlockChange[] NetHandlerPlayClient.handleMultiBlockChange @Overwrite
6 level.dat ChunkAPICoreModContainer.getDataForWriting / readData(WorldAccessContainer) —

全部靠 mixin 挂载,没有任何 Forge 事件。 每条路径的详细顺序见 序列化流水线。

数据包格式

区块包体(DataRegistryImpl.writeToBuffer / readFromBuffer,小端序):

putInt(manager 数量)
for each manager(按 ordering 升序):
    putInt(id 的 UTF-8 字节长度) + id 字节
    putInt(该段实际长度)            ← 预留 4 字节槽后回填
    manager.writeToBuffer(chunk, subChunkMask, forceUpdate, slice)

单方块包体(writeBlockPacketToBuffer)没有长度前缀,靠注册键精确查找:

writeInt(blockPacketManagers 数量)
for each manager(按 ordering 升序):
    writeStringToBuffer(ord.id)
    manager.writeBlockPacketToBuffer(packet, buffer)

⚠️ 两条路径的容错性不同:区块包遇到未注册的 manager 会打 error 并跳过该段(可降级),单方块包遇到未注册的 manager 直接 NPE。

核心机制速览

包体积预算

S21PacketChunkData.func_149275_c() 被 @Overwrite 成 DataRegistryImpl.maxPacketSize(),原版返回固定 12288,ChunkAPI 换成按注册表累加的动态值:

maxPacketSize 初值 4
每注册一个 PacketDataManager: maxPacketSize += 4 + id.length + 4 + maxPacketSize()
内建 manager 注册键 maxPacketSize()
BlockIDManager minecraft:blockid 98306
MetadataManager minecraft:metadata 32768
BlocklightManager minecraft:blocklight 32768
SkylightManager minecraft:skylight 32768
BiomeManager minecraft:biome 256

⚠️ BlockIDManager 一家就占了预算的一半(2 + 16 × (4096 LSB + 2048 MSB)),且这 5 个 manager 全部 ordering 传 0(ChunkAPI.init),实际遍历序退化成按 id 字典序。全部累加后 maxPacketSize = 197018(按公式推算)。

读档兼容闸门

这是本 mod 唯一有用户可见行为的部分。level.dat 里记录每个第三方 manager 的 version 与 uninstallMessage(跳过所有 minecraft: 前缀)。读档时 DataRegistryImpl.verifyManagerCompatibility 比对三类差异:

差异 报告内容
已移除的 manager id + 存档版本 + "Uninstall information: "
新增的 manager id + 当前版本 + "Install information: "(newInstallDescription() 非 null 时)
不兼容版本变更 "<id> has changed versions from <旧> to <新>" + "Upgrade information: "

任一命中 → 拼风险警告 → StartupQuery.confirm(...)(用户不确认就 abort()) → ZipperUtil.backupWorld()(备份失败也 abort())。

⚠️ 内建 6 个 manager 继承 VanillaManager 的默认实现(version() 返回 ""、三个提示方法分别返回 null/""/null),因此永远不会触发任何一类警告。

Mixin 分组与加载时机

Mixin 枚举 5 个分组,4 个 EARLY + 1 个 LATE:

分组 Phase 条件 mixin
VanillaCore EARLY !hasThermos() common/vanilla/S26PacketMapChunkBulkMixin
ThermosCore EARLY hasThermos() common/thermos/S26PacketMapChunkBulkMixin
CommonCore EARLY 无条件 base/ 4 个 + client/vanilla/NetHandlerPlayClientMixin
Core_NoSpool EARLY avoid(Spool) base/AnvilChunkLoaderMixin + client/vanilla/ChunkMixin
Compat_LookingGlass LATE require(LookingGlass) common/lookingglass/PacketChunkInfoMixin

⚠️ 只有 Core_NoSpool 带 avoid(Spool)——Spool 存在时被跳过的只有 AnvilChunkLoaderMixin 与 ChunkMixin,CommonCore 的 5 个 mixin 照常应用。 ⚠️ hasThermos() 的实现是 Class.forName("thermos.Thermos")。 ⚠️ 两份 mixin JSON 都没有 "mixins" 数组,类清单运行时注入。

Access Transformer(4 条)

public-f net.minecraft.world.chunk.NibbleArray field_76585_a # data
public-f net.minecraft.world.chunk.NibbleArray field_76583_b # depthBits
public-f net.minecraft.world.chunk.NibbleArray field_76584_c # depthBitsPlusFour
public net.minecraft.server.management.PlayerManager$PlayerInstance

⚠️ CoreLoadingPlugin.getAccessTransformerClass() 返回 null——AT 走 build.gradle.kts 的 accessTransformerFile,不经 IFMLLoadingPlugin。 ⚠️ 前 3 条用 -f 保留 final,但 ArrayUtil.copyArray(NibbleArray, NibbleArray) 里确实对这两个字段做了赋值(源码与 AT 不一致)。

源码中的不一致(均为原文,未修正)

位置 现象
BlocklightManager.cloneSubChunk from.setBlocklightArray(...) 应为 to.(对照 SkylightManager / MetadataManager 同名方法)——克隆时 blocklight 数据留在源 subChunk,目标不被替换
ChunkAPI.init 的注册顺序 6 个 manager 全传 ordering 0,实际遍历序按 id 字典序而非源码书写顺序
readLevelDat 第 392 行 val managerCompat = verifyManagerCompatibility(tag) 命名与语义相反(名为 compat 实为差异报告)
S22PacketMultiBlockChangeMixin.writePacketData subPackets == null 分支写 writeInt(0),正常分支写 writeVarIntToBuffer(length)——两分支编码方式不一致,读侧按 varInt 解析
readBlockPacketFromBuffer blockPacketManagers.get(id) 返回 null 时直接 NPE,无降级(区块包路径有)
ChunkMixin.fillChunk 形参 subChunkMSBMask 被完全忽略(MSB 信息由 BlockIDManager 自己的 2 字节头承载)
两份 S26PacketMapChunkBulkMixin.readPacketData subChunkMSBMasks 分配为 new int[chunkCount] 但从不写入也从不读取
SkylightManager.writeSubChunkToNBT 无天空维度下写的全 0 数组长度取自 getBlocklightArray().data.length 而非 getSkylightArray()
NibbleManager 两个 Chunk 级方法 只判 subChunk != null,不判 getNibbleArray(subChunk).data != null
ArrayUtil 的 @Contract 声明只有 "!null, null -> new",但实现里 src.length != dst.length 也返回新数组
ArrayUtil 与 AT 的 -f 对 final 的 depthBits / depthBitsPlusFour 赋值
LightingManager 在 internal.vanilla 包直接读写 chunk.heightMap / chunk.isLightPopulated,既无 AT 规则也不同包,源码未说明可见性来源
PacketChunkInfoMixin.handle 解压失败重请求时把 subChunkMask 传给 PacketRequestChunk.createPacket(xPos, subChunkMask, zPos, dim);转发调用把 yMSBPos 硬编码 (short)0
MixinHelper.server(...) / Mixin.server(...) 无调用点,mixins/server/ 目录不存在(只有 client/ 与 common/)
plugin.fplib.TargetMod 与 plugin.TargetMod 枚举重名结构,无任何引用
mcmod.info 的 dependencies [] 空数组,但实际硬依赖 gtnhmixins / lombok / org.jetbrains:annotations,可选 lookingglass
ChunkAPICoreModContainer 的 dependencies 加了一条 DefaultArtifactVersion(Tags.MOD_ID, Tags.MOD_VERSION),core 容器依赖自己的父 mod

本 mod 没有的东西(grep 证据)

以下断言均已在 /Users/evlos/a/mirror/ChunkAPI/src/main/java/ 下全量检索核实:

维度 检索命令 结果
方块注册 grep -rn "registerBlock|new Block(|Block.register" 0 命中
物品注册 grep -rn "registerItem|new Item(|Item.register" 0 命中
实体 / TileEntity 注册 grep -rn "registerEntity|registerTileEntity|registerModEntity|EntityRegistry" 0 命中
世界生成 / 人口生成 / 结构 / 特征 grep -rn "IWorldGenerator|WorldGen|populate(|PopulateChunk|addGenerator|MapGenStructure|Feature|BiomeProvider|WorldProvider|registerDimension" 0 命中 ⚠️ 这是对任务简报最关键的一条修正
Forge 事件 grep -rn "@SubscribeEvent|net.minecraftforge.event|Event(" 0 命中(本 mod 不声明也不监听任何事件)
指令 grep -rn "CommandHandler|ICommand|addChatCommand|CommandBase|getCommandName" 0 命中
热键 grep -rn "KeyBinding|keybinding|registerKeyBinding" 0 命中
配置 grep -rn "Configuration|@Config|ForgeConfigSpec|config.get|Configuration.getConfig" 0 命中,且 src/main/resources 下无任何 *.cfg 模板——不生成配置文件
附魔 / Buff / 成就 / 群系注册 grep -rn "Enchantment|PotionEffect|Achievement|BiomeGen|registerBiome" 0 命中
多方块 grep -rn "extends TileEntity|Multiblock|IMultiblock" 0 命中
IClassTransformer grep -rn "IClassTransformer" 0 命中;getASMTransformerClass() 返回 new String[0]
服务端专属 mixin ls src/.../mixin/mixins/ 只有 client/ 与 common/,无 server/
assets/ 目录 ls src/main/resources/assets 不存在——无 zh_CN.lang、无模型、无贴图、无配方
矿词典 / 配方 / 液体 / 能量 grep -rn "OreDictionary|addMapping|GameRegistry.addRecipe|IFluidHandler|IEnergyHandler" 0 命中
唯一 @Mod 注解 grep -rn "@Mod(" 仅 1 处:internal/ChunkAPI.java:36
@Mod.EventHandler 方法体 读 internal/ChunkAPI.java:43-50 只有 6 行 DataRegistry.registerDataManager(...),无代理、无渲染、无网络注册

⚠️ 关于"实体"的唯一两处 net.minecraft.entity import 都与实体内容无关:AnvilChunkLoaderMixin:35 的 net.minecraft.entity.Entity(用于写盘时遍历实体列表)与 PacketChunkInfoMixin:35 的 net.minecraft.entity.player.EntityPlayer(LookingGlass 封包的接收者参数)。

因此只建了 setting/ 一个目录;block、item、entity、multiblock、enchantment、dimension、keybinding、potioneffect、biome、structure、achievement、item-effect 共 12 个维度无内容,未建目录、未在正文里留占位。

相关条目