反射与日志助手

基本信息

属性 值
包 modtweaker2.helpers
文件数 6 个(829 行)
核心类 ReflectionHelper(183 行)、LogHelper(190 行)
反射方式 java.lang.reflect.Field#setAccessible + Class.forName

功能

ModTweaker 需要读写大量第三方模组的私有静态注册表(配方表、战利品表、物质表……),而这些字段在 1.7.10 上大多没有公开 API。modtweaker2.helpers 下的 6 个类承担这层「越过可见性边界」的适配工作。

助手类 行数 作用
ReflectionHelper 183 通用反射工具:取字段、取构造器、实例化、改私有静态值
LogHelper 190 日志分级 + 把对象转成可直接粘进脚本的字符串
InputHelper 191 ItemStack / IItemStack / Object[] 之间的互转
StackHelper 131 物品配方的增删与「是否同配方」判定
StringHelper 75 字符串工具
ForgeHelper 59 抓取 Forge 自身的私有表并造 SeedEntry

ReflectionHelper

6 个反射入口,全部走 setAccessible(true):

方法 取什么
getObject(Object, String...) 实例的私有字段,按字段名列表依次试,返回第一个命中的
getFinalObject(Object, String...) 同上,但先抹掉 final 修饰符再取值
getStaticObject(Class, String...) 类的私有静态字段
getStaticObject(String, String...) 先 Class.forName 再转调上一重载
getConstructor(String, Class...) / getConstructor(Class, Class...) 指定参数类型的构造器
getInstance(Constructor, Object...) 用构造器造实例
setPrivateValue(Class, String, int) 改私有 static int 字段

「字段名列表」的设计是为了跨 MCP 映射名容错。ForgeHelper 是典型例子——同一份数据同时给出开发名与混淆名(ForgeHelper.java:34-36):

translate = getFinalObject(
        getStaticObject(StatCollector.class, "localizedName", "field_74839_a"),
        "languageList",
        "field_74816_c");

getFinalObject 的实现(ReflectionHelper.java:105-122)先取 Field.class 自身的 modifiers 字段,抹掉目标字段的 FINAL 位后再读——这是 1.7.10 / Java 8 上写 final 静态表的常规手法(modifiersField.setInt(result, result.getModifiers() & ~Modifier.FINAL),:113)。

ForgeHelper

静态初始化块一次性抓三张表(ForgeHelper.java:29-37):

字段 来源
seeds ForgeHooks.seedList
loot ChestGenHooks.chestInfo
translate StatCollector.localizedName → languageList

拿到 seeds 之后,getSeedEntry(ItemStack, int)(ForgeHelper.java:41-52)用 ForgeHooks$SeedEntry 这个包私有内部类的构造器造条目,失败时显式抛 NullPointerException。这是 Ex Nihilo 的种子配方能被脚本改写的基础。

LogHelper

日志分级

logError / logWarning / logInfo / print 四个出口(LogHelper.java:52-70)都统一给消息加 [ModTweaker2] 前缀。logError 有一个带 Throwable 的重载(LogHelper.java:56-58),ReflectionHelper 的每个 catch 块都调用它——异常堆栈不会被丢弃。

对象 → 脚本字面量

getStackDescription(Object)(LogHelper.java:74-114)把任意 Java 对象转成能直接粘进 Zenscript 的字符串,是所有 /minetweaker dump 指令的公共出口:

输入 输出形式
ItemStack / IIngredient MCItemStack.toString()
FluidStack <liquid:name> 或 <liquid:name> * 数量
Block 视为 meta 0 的 ItemStack
mekanism.api.gas.GasStack MCGasStack.toString(),需 TweakerPlugin.isLoaded("Mekanism")
thaumcraft 的 AspectStack / AspectList 要素列表,需 isLoaded("Thaumcraft")
String 若 OreDictionary.getOres() 非空则输出 <ore:name>,否则输出带引号原文
List / Object[] 递归成 [a, b, c]
null 字符串 null

这四处 TweakerPlugin.isLoaded 守卫(LogHelper.java:80、:82、:84)是防止在 Mekanism / Thaumcraft 缺席时对缺失类做 instanceof 而炸掉整条 dump 的关键。

getCraftingDescription(IRecipe)(LogHelper.java:159-165)分派到 4 个重载,覆盖 ShapelessOreRecipe / ShapedOreRecipe / ShapelessRecipes / ShapedRecipes,输出成方括号列表。有序配方用 ReflectionHelper.getObject(recipe, "width"/"height") 读尺寸(LogHelper.java:177-178)——注意这里读的正是混淆名,反射工具的字段名列表容错在此起了作用。

静默失败面

6 个 static {} 初始化块用完全空的 catch 吞掉初始化异常:

文件 行
modtweaker2/helpers/ForgeHelper.java 37
modtweaker2/mods/thaumcraft/ThaumcraftHelper.java 28
modtweaker2/mods/mariculture/MaricultureHelper.java 28
modtweaker2/mods/factorization/FactorizationHelper.java 29
modtweaker2/mods/metallurgy/MetallurgyHelper.java 25
modtweaker2/mods/tconstruct/TConstructHelper.java 51

全部是 } catch (Exception e) {} 形式,例如 ThaumcraftHelper 抓 ThaumcraftApi 的私有 warpMap(ThaumcraftHelper.java:25-28),FactorizationHelper 连续抓两个 TileEntity* 的 recipes 静态表(FactorizationHelper.java:24-29)。

后果可从代码直接读出:这些 static 字段若初始化失败会保持 null,且不留任何日志,直到某条脚本方法在空引用上抛 NPE 才暴露。TConstructHelper 一次抓 9 张表(TConstructHelper.java:38-51),是受影响面最大的一处。此处按代码原样记录,不据此断言缺陷。

相关条目