太吾绘卷存档检查器
这个技能解决什么问题
太吾绘卷的 local.sav 是纯二进制、完全 ID 化的存档:字节流里没有任何字段名、没有任何中文字符串,只有成片的整数 ID。直接按字节解析等于从零逆向一整套序列化协议,成本极高且极易因游戏版本更新而失效。
本技能采用完全不同的思路:
> 不解析格式,而是借用游戏自己的代码。把一段 C# 钩子注入游戏自带的 GameData.exe 进程,在它的 Main 运行前执行,调用游戏自己的 LocalArchiveFile.Load() 把存档读进内存,然后用反射直接读内存里的对象图。
好处:不需要理解序列化格式;版本更新只要字段名没大改就仍然可用;拿到的数据是游戏自己认定的权威数据(含中文名、区域名),不存在"猜错 ID 含义"的风险。
何时使用
- 用户要查某个 NPC 的完整情报:属性、资质、功法、特性、装备、关系、门派
- 用户要按条件筛 NPC:某门派成员、某年龄段、某性别的角色名单
- 用户问某件道具/功法书在谁身上、有多少本、在哪
- 用户问某件道具在哪买:哪个商会、第几档货单、好感度够不够、现在有没有商队带着
- 用户问某件道具存在哪个仓库:太吾村私库/货仓、哪个门派的库房、外院/内院/禁院哪一档、支持度够不够解锁
- 用户想统计存档里某类物品或某类角色的分布
- 需要读取
local.sav内的角色、物品、地图、势力、商会、仓库数据
不适用:只想浏览角色列表(游戏内更快);不能运行游戏程序的环境;非太吾绘卷的存档。
两个查询入口
A. NPC 情报查询(run_npc.sh)
# 全量清单(默认前 200 人,一行一人)
bash <skill-dir>/scripts/run_npc.sh
# 指定角色完整情报
TAIWU_MODE=detail TAIWU_NPC=6701 bash <skill-dir>/scripts/run_npc.sh
# 多个角色
TAIWU_MODE=detail TAIWU_NPC=6701,5553 bash <skill-dir>/scripts/run_npc.sh
# 条件筛选:血犼教 18~30 岁女性
TAIWU_FILTER="gender=0;minage=18;maxage=30;org=15" bash <skill-dir>/scripts/run_npc.sh
# 结构探查(不知道有什么字段时先跑这个)
TAIWU_MODE=probe bash <skill-dir>/scripts/run_npc.sh
TAIWU_FILTER 支持的键:gender(0女/1男) minage maxage org(势力模板Id) grade principal=0|1 area bi=0|1 trans=0|1 minhealth,分号分隔。
B. 物品查询(持有者 + 售卖渠道 + 存放仓库 + 耐久/篇章)(run_extract.sh)
# 功法书:谁身上有 + 商会/商队货单 + 哪个仓库 + 耐久与篇章
TAIWU_ITEMS=674 TAIWU_ITEM_TABLE=SkillBook bash <skill-dir>/scripts/run_extract.sh
# 兵器:ID 空间按类型独立,查货单时强烈建议显式给 ItemType
TAIWU_ITEMS=111 TAIWU_ITEM_TYPES=0 bash <skill-dir>/scripts/run_extract.sh
# 多目标(ID 与类型按逗号对齐)
TAIWU_ITEMS=111,120 TAIWU_ITEM_TYPES=0,0 bash <skill-dir>/scripts/run_extract.sh
# 按状态筛选:第三个篇章完整的《越王秘藏》
TAIWU_ITEMS=59 TAIWU_ITEM_TABLE=SkillBook TAIWU_PAGE=3 TAIWU_PAGE_STATE=complete \
bash <skill-dir>/scripts/run_extract.sh
# 只看耐久 80% 以上的
TAIWU_ITEMS=59 TAIWU_ITEM_TABLE=SkillBook TAIWU_DUR_MIN=80 \
bash <skill-dir>/scripts/run_extract.sh
TAIWU_PROBE=1 bash <skill-dir>/scripts/run_extract.sh # 结构探查
报告分五段:target id resolution(这个数字在几十张表里分别是什么)、
merchant channels(商会货单命中 + 商队实持)、warehouse channels(太吾村仓库 + 各地库房分层命中)、
item states(耐久分布 + 全书各篇章完整度分布)、RESULT(持有者名单)、
item detail(逐件列式明细,每篇章一列,见下文「道具什么状态」)。
ItemType 怎么定,按优先级:
TAIWU_ITEM_TYPES显式指定;TAIWU_ITEM_TABLE(如SkillBook)反推;- 扫描持有物得到的实际类型;
- 逐张 Item 表试
GetItem+ 语言表能否翻译,候选唯一才采用。
注意:同一个 TemplateId 会在多张表里同时存在(实测 59 同时是《越王秘藏》、食物、药)。
所以 RESULT 的统计必须 ItemType + TemplateId 双重匹配,只比 TemplateId 会把食物和药算成书,
数量会虚高(实测虚高到 201 本,真实只有 77 本)。
全失败时报告会明确写「无法确定类型,货架匹配跳过」,此时不要编答案,让用户补 TAIWU_ITEM_TYPES。
通用环境变量
| 变量 | 说明 |
|---|---|
TAIWU_WORLD |
存档目录名,默认 world_2 |
TAIWU_ROOT |
游戏根目录(含 Backend/ 与 SaveGames/),自动探测常见 Steam 路径 |
TAIWU_WORK |
工作目录,默认 ./taiwu_work。两个脚本共用,只需复制一次 120MB 运行环境 |
TAIWU_LIMIT |
summary 模式最大输出条数,默认 200 |
TAIWU_ITEM_TYPES |
与 TAIWU_ITEMS 一一对应的 ItemType 数值,逗号分隔(13 类对照见下表) |
TAIWU_GAME |
ArchiveBaseDir 落点,留空用运行目录;只需非空即可 |
TAIWU_PAGE |
篇章筛选:0=总纲,1..5=第 N 篇章(口语「第三篇」填 3),不填不筛 |
TAIWU_PAGE_STATE |
篇章完整度:complete/incomplete/lost,也接受 0/1/2 |
TAIWU_DUR_MIN |
耐久百分比下限 0~100,如 80 表示只留耐久 ≥80% 的 |
报告落地:默认写成 Markdown 文件
查询结果的默认交付物是 .md 文件,不是聊天里贴一段终端输出。
两个脚本输出的 *_report.txt 只是原始日志,是给你读的,不是给用户看的。
路径约定:<工作目录>/reports/<姓名>-charId<id>.md(物品查询用 reports/<物品名>-id<模板Id>.md)。
排版铁律:
- 文件开头必须有
> **一句话**:…摘要,把最重要的 2~3 个结论先说完,不要让用户自己从表格里找。 - 先总表后细节:基本档案做成两列表(项 / 值),不要一堆
key : value竖排文本。 - 长列表要分栏:资质、装备、背包这类 10~40 项的内容,用「3 组并排的表格」或按门类分组,
不要一行一项竖着拉 40 行。 - 数量要算:
43 条 / 合并 44 件、装配 27 / 已学 32、10 条实质关系 / 209 条总数都写出来,
用户关心的是「有没有缺口」,不是原始数字。 - 差值要标:当前值 vs 基础值要算成长率;门槛类要写「差多少」(如库房「支持度差 40」)。
- 发现异常要单独点出来:如「有两门 G5 功法没装配」「名誉已触底 -500」,
这类判断才是报告的价值所在,光抄字段没有意义。 - 结尾必须有「数据来源与可信度」小节:存档路径、
load结果、启动自检行、角色/关系总数。
没有这一节,读者无法判断数字可不可信。 - 不要在 md 里贴
slot0 : xxx (type0/tmpl17)这种内部格式,翻译成人话(槽位表两列并排)。
生成 md 后,聊天里仍要给一份精简版结论(结论 + 关键表格 3~5 行 + 异常点),
再用 present_files 把 md 文件递给用户——不要让用户为了看结论去打开文件。
必须知道的十个机制
这些是踩了大量坑才确认的,改动模板时务必保留:
-
读档前必须全量初始化配置表。对
Config.ConfigCollection.NameMap(312 张表)中每个IConfigData调用Init()。否则WorldDomain.OnLoadWorld会在ConfigData.GetItem()处抛NullReferenceException,读档彻底失败。
- 用Init(),不要用ImportFromFiles(dir)——后者会清空内存数据。
- 极少数表(LocalSurnames/LocalNames/GlobalConfig等 6 张)Init()后仍为空,属正常。 -
读档前必须补完整个"启动序列",缺一项就可能整片域不加载(本技能最关键的一条)。
LocalArchiveFile.Load()→ReadContent()会按 DomainId 顺序逐域OnLoadWorld,
前面任一域抛异常,后面所有域全部跳过。
钩子跑在 Main 之前,游戏在 Main 里做的初始化一件都还没发生。必须按下面顺序补:
| # | 补什么 | 不补的后果 |
|---|---|---|
| 1 | CharacterActionPlanner.Instance.Initialize() |
_actionNodes 为 0,Character 域 GetActionNode 索引越界抛 ArgumentOutOfRangeException,域 4 就断 |
| 2 | 静态属性 ArchiveData.Common.ArchiveBaseDir 设成任意非空目录 |
TaiwuEventDomain 里 Path.Combine(ArchiveBaseDir, "EventScriptRuntimeSettings.json") 抛 ArgumentNullException,域 12 断 |
| 3 | TaiwuEventDomain._scriptRuntime 手动构造 EventScriptRuntime(DataContext, Boolean) |
域 12 InitRuntimeEnvironment 抛 NRE |
| 4 | ObjectPoolManager.Initialize() |
ObjectPool<T>.Instance 为 null,域 19 CheckCombatSkillOrderPlan 里 ldsfld + callvirt Get NRE |
| 5 | Config.DynamicMapping.Initialize() |
TwelveImmortalsFeatureId2TemplateId 为 null,Character.IgnoreFeature NRE |
| 6 | 各域 static Initialize*()(9 个) |
CombatSkillDomain._learnableCombatSkillsCache 为 null,库房供给初始化越界 |
| 7 | 各域实例回调 InitializeOnInitializeGameDataModule() |
CombatSkillDomain.EquipAddPropertyDict 为 null,GetCharPropertyBonus 的 ldelem.ref NRE |
| 8 | DlcManager._dlcInfoList / _enabledDlcIds 置空集合 |
OnLoadedArchiveData 里 ldsfld _enabledDlcIds + GetEnumerator NRE |
- 八项全部补齐后实测
Load()返回 OK(不再 PARTIAL),域 0~20 全部加载,
_actionNodes389、_goalNodes276。 - 第 7 项里
TaiwuEventDomain会抛ArgumentNullException(它需要真实主线程DataContext),
这是唯一允许失败的一个,不影响任何查询。自检行显示initCb:20/21即正常。 - 报告头部四行是自检点:
planner _actionNodes=389 load fixes = ArchiveBaseDir scriptRuntime startup boot = pool:ok dmap:ok initStatic:9/9 initCb:20/21 dlc:2/2 load = OK ← 域 0~20 全部加载,派生值可用
若pool或dmap不是ok、initCb明显偏少,就一定会退回load = PARTIAL,
此时所有Calc*派生值都变N/A。 TaiwuEventDomain.MainThreadDataContext不是阻塞点:它是普通实例字段(initonly=False),
真正卡住的是缺_scriptRuntime。- 域 14 能否加载直接决定「商会/商队」功能:没补前置时
_caravanData=0,查什么都是「不在售」。
-
派生值必须调
Calc*方法,且要区分"算不出"与"真的是 0"。
- 存档只存基础值,_combatSkillAttainments(造诣)、_personalities(性格)、_maxMainAttributes、_combatPower、_morality、_fame等字段全库都是 0,直接读会得到错误答案。
- 正确做法:调CalcCombatSkillAttainments()/CalcPersonalities()/CalcMorality()/CalcFame()/CalcMaxNeili()/CalcNeiliAllocation()等(Character上有 40+ 个Calc*方法)。
- 启动序列补齐后,实测 10 个派生值全部可用(同一角色世界2太吾):
CalcCombatPower=99912CalcPersonalitiesCalcAttraction=900CalcPhysiologicalAge=24
CalcMaxHealth=781CalcMaxNeili=1153CalcCombatSkillAttainmentsCalcLifeSkillAttainments
CalcMorality=-250CalcFame=53。
- 若仍是N/A,先看报告头部的startup boot行,不要怀疑公式本身。
- 绝不能用 0 兜底——0 是有效数据,会被误读成"此人一无所有"。 -
姓名不能靠查表拼。调用
FullName.GetName(gender, customTexts)——它返回的是语言键而非汉字,且给定名是两个键直接拼接的(如Name_611Name_97_Man_Apart_2)。必须在第二次出现Name_处切开,再分别查Name_language.txt。
-Type含CustomSurname(4)或CustomGivenName(8)位时GetName会抛异常,此时姓名原文在DomainManager.World._customTexts(Dictionary<int,string>)。 -
AreaId是区域序号,不是配置 ID。必须走Map._areas[AreaId]._templateId→Config.MapArea.GetItem(tmpl).Name→MapArea_language.txt。直接用Name_<序号>查会得到完全错误的地名(实测序号 42 会误读成"涪江",实际是"扬州")。 -
ID 空间按表独立。同一个数字在几十张表里都存在(实测 674 同时是
SkillBook=黄坟砂 与CharacterFeature=无先玄元)。绝不能"猜一张表",要么显式指定表名,要么列出全部候选。
- 物品有 13 个ItemType,与表名一一对应:0 Weapon / 1 Armor / 2 Accessory / 3 Clothing / 4 Carrier / 5 Material / 6 CraftTool / 7 Food / 8 Medicine / 9 TeaWine / 10 SkillBook / 11 Cricket / 12 Misc。
-ItemType是const class不是 enum,Enum.GetValues无效,要读FieldInfo.GetRawConstantValue()。
- 判货架命中必须同时匹配ItemType与 ID 区间,只比 ID 会大面积误报。 -
存档只读,永远读副本。先把存档目录复制到工作区,钩子只读副本。脚本在运行前后各打印一次真实
local.sav的 mtime 自证未被改动。 -
商人卖什么只看
Config.Merchant一张表。54 行 = 9 种商会 × 各自等级档,每行 14 个货架Goods0~Goods13,货架元素是
PresetItemTemplateIdGroup{ItemType, StartId, GroupLength},含义为「ItemType类里[StartId, StartId+GroupLength)的连续 ID」。
- 等级档 =Level字段;解锁门槛FavorRequirement只与好感度界面值 0~100 比较,
不能拿_merchantFavorability的累计点数(几万~几十万)去比。
- 商队的GoodsList0..6下标就是等级档,且高档累积低档(Lv3 商队GoodsList0~3非空、4~6空)。
报「在哪一级列表」要按实际命中的GoodsList序号,不能只看MaxLevel。
-MerchantTemplateId是Config.Merchant的行号,不是 0~8 的商会编号。
- 春日集市 =Config.Adventure模板 77,摆摊仍走各商会的等级档,没有独立货单。
-Config.ShopEvent是商队遭遇/采买事件(ItemList.Amount有正有负),不是货架,不要拿它判在售。 -
仓库分两层:太吾村的固定两个容器 + 各地定居点的三层库房。
- 太吾村:TaiwuDomain._warehouse(对应TaiwuVillageStorageType.Warehouse=1,私库)、
_stock(对应Stock=3,货仓)。取法TaiwuDomain.GetWarehouse()或直接读字段。
- 各地库房:OrganizationDomain._settlements(本存档 110 个定居点),每个Settlement
有一座SettlementLayeredTreasuries,装 3 个SettlementTreasury。
取法OrganizationDomain.GetTreasury(Int16 settlementId, SByte layerIndex),
物品在返回值的Inventory.Items(Dictionary<ItemKey,int>)。
- 档位 =SettlementTreasuryLayers枚举:Shallow=0 外院库房/Mid=1 内院库房/Deep=2 禁院库房。
准入门槛在Config.GlobalConfig单例里(游戏自己把 Require 拼成了Rquire,照抄):
TreasuryRquireApprovingMid=40、TreasuryRquireApprovingHigh=70(门派支持度),
并行途径TreasuryRquireSpiritualDebtMid=400、TreasuryRquireSpiritualDebtHigh=700(地区恩义)。
- 支持度用Settlement.CalcApprovingRate()(已按GetApprovingRateUpperLimit()截断,本存档上限 400)。
不要用_sectCharacters里逐人GetApprovingRate()累加——那是原始贡献值,实测可达上万。
-Settlement._treasuries是懒加载字段。不先调一次GetTreasury(id, layer),反射读它永远是null,
会误判成"这个据点没有库房"。这是本轮最大的坑。 -
耐久与书籍篇章在「实例」上,不在背包条目上。
- 背包 / 库房 / 货架存的都是
ItemKey(ItemTypeTemplateIdModificationStateId四个字段)。
真正的实例对象要拿ItemKey.Id去ItemDomain的 13 个字典里换:
_weapons_armors_accessories_clothing_carriers_materials_craftTools
_foods_medicines_teaWines_skillBooks_crickets_misc
(本存档合计 89589 件,Id 全局唯一,实测合并无冲突)。 - 耐久:
ItemBase.MaxDurability/CurrDurability(Int16 字段,也可走GetMaxDurability())。
所有物品都有这两个字段;MaxDurability=0表示该物品本就不磨损(如材料)。 - 书籍篇章只有
SkillBook子类才有,两个字段: _pageTypes(Byte):低 3 位 = 总纲类型 0~4(承/合/解/异/独),
bit3~bit7 = 第 1~5 篇章的正逆练方向(0=正练 1=逆练,位序1 << (pageId + 2))。_pageIncompleteState(UInt16):每页 2 bit,起始位pageId * 2,
取值0=完整 1=残缺 2=亡佚。- 页数由
GetPageCount()给出,不是常数:功法书 6 页(page0=总纲,page1~5=五篇章),
技艺书 5 页(page0~4=五篇章,无总纲)。判断依据是IsCombatSkillBook()。 - 位布局是从
SkillBookStateHelper.SetOutlinePageType/SetNormalPageType/
SetPageIncompleteState的 IL 反出来的,不是猜的:
SetOutline=(x & 248) | type;SetNormal=1 << (pageId + 2);
SetIncomplete=shift = pageId * 2,(x & ~(3<<shift)) | (state<<shift)。
GenerateCombatPageTypes里CheckPercentProb ? 0 : 1证明 0=正练。 - 自洽校验:把全库 3001 本书按上述规则解码,与游戏自己的
AnyCompletePage()逐本比对,
0 处不一致 —— 这套解码是可信的。
- 背包 / 库房 / 货架存的都是
道具「在哪买」怎么读结果
merchant channels 段给出两层信息,缺一不可:
[111] 铁梭子 ItemType=0(Weapon) 来源=指定
商会货单命中 1 个等级档: ← 配置侧:理论可售
✓ 大武魁商号 · 武魁·一(Lv0) 需好感度 0 → 已解锁
货架 Goods0(权重 3/40 ≈7% 季[春---]) ← 出货概率 + 哪一季会供
现有商队持有 12 支: ← 运行时侧:此刻真的带着
● 商队#377 大武魁商号 · 武魁·一(商会自身 Lv0) 命中 Lv0列表x1
- 配置侧命中 ≠ 现在买得到。命中只说明「该商会的该等级档货架覆盖此物」;
还得看→ 已解锁/未解锁(好感度是否达到FavorRequirement),以及当季SeasonsGoodsRate/GoodsRate是否非 0。 权重 3/40 ≈7% 季[春---]读法:该货架在该档的抽取权重是 3(合计 40),只在春季供货,夏秋冬为-;权重0 不出货表示该货架在本档永不启用。- 只能说概率,不能说结果。刷新是加权随机抽取,不能断言「下个月一定有」。
- 运行时侧才是「现在有货」的证据:哪支商队(
charId)、商会、自身等级、命中第几级列表、几件。 - 两边都是 0 时,结论是「商会/商队目前不卖此物」,这是有效结论,不要说「查不到」。
- 春日集市未开(
春日集市=0 处)时仍要报配置侧结果。
道具「存在哪个仓库」怎么读结果
warehouse channels 段分两块:太吾村仓库(一行)与各地库房(按定居点逐条):
[59] 《越王秘藏》 ItemType=10(SkillBook) 来源=表名
库房命中 1 层,合计 2 件:
◆ 铸剑山庄(定居点#59 地块 25/142) 支持度 0/400 · 恩义 无记录 ← 哪个势力 + 两条准入门槛的当前值
> 内院库房(Lv1档) 需支持度>=40 或恩义>=400 [未解锁](支持度差 40,本区域无恩义记录) 2 件
→ 其中 1 层(2 件)尚未解锁,需提升该门派支持度或当地地区恩义才能取用
太吾村:私库 无 · 货仓 无
- 档位就是答案的一半:外院(
Shallow,无门槛)→ 内院(Mid,支持度≥40)→ 禁院(Deep,支持度≥70),
与品级对应(稀松平常 / 瑰奇精良 / 惊艳绝世)。报结果时必须写清是哪一档。 - [未解锁] ≠ 不存在。道具确实躺在那里,只是当前支持度不够拿不到,要如实说明,别写成"没有"。
- 解锁是「支持度 或 地区恩义」二选一:支持度 40/70,恩义 400/700(
ExtraDomain._areaSpiritualDebt)。
判定时两条都要算,[已解锁](地区恩义达标)这种情形必须体现出来,不能只看支持度。 - 报告未解锁时要写出差多少(
支持度差 40/恩义差 210/本区域无恩义记录),否则用户不知道该刷哪个。 - 报告只列有命中的库房。全无命中时写「N 个定居点共 3N 层中均无此物」,这是有效结论。
- 太吾村只有两个容器(私库
_warehouse/ 货仓_stock),没有第三处。
道具「什么状态」怎么读结果
item states 段是这本书在全存档的状态全貌(不受筛选影响):
[59] 《越王秘藏》 ItemType=10(SkillBook)
存档内该书实例合计 88 件(含尚未发放/无人持有的)
耐久:满耐久 74 · 已磨损 14 · 濒临损坏(<30%) 0 平均 17/18
页数 = 5(技艺书:5 篇章,无总纲)
第壹篇 完整 66 · 残缺 19 · 亡佚 3
第叁篇 完整 27 · 残缺 47 · 亡佚 14
item detail 段是逐件的实例情报。涉及书籍时一律用列式表头:每个篇章一列,一行一本,
不要退回成「篇幅 壹=完整 贰=残缺 …」挤在一列里的写法。这条格式要求对脚本输出、聊天回复、
交付的 md/docx 报告同样适用。
【功法书 · 6 页:总纲 + 5 篇章】25 件
charId | 姓名 | 区域 | 实例 | 耐久 | 持有者研读 | 总纲 | 第壹篇 | 第贰篇 | 第叁篇 | 第肆篇 | 第伍篇
5506 | 司寇伏龙 | 血犼谷 | #181189 | 16/18 | 已读完 | 解 | 逆练·完整 | 正练·完整 | 正练·残缺 | 逆练·残缺 | 正练·残缺
5530 | 卜清 | 血犼谷 | #158093 | 33/35 | 在读 | 合 | 正练·残缺 | 逆练·完整 | 逆练·完整 | 逆练·完整 | 逆练·残缺
【技艺书 · 5 页:5 篇章,无总纲】3 件
charId | 姓名 | 区域 | 实例 | 耐久 | 持有者研读 | 第壹篇 | 第贰篇 | 第叁篇 | 第肆篇 | 第伍篇
2088 | 范杏英 | 莲花山 | #68851 | 13/13 | 未读 | 完整 | 残缺 | 亡佚 | 残缺 | 残缺
【非书籍 · 只列耐久】2 件
charId | 姓名 | 区域 | 实例 | 耐久
3641 | 刘世栋 | 空桑山 | #18929 | 96/96
- 自动分三组,各自独立表头:功法书(6 页,多一列总纲,篇章格写明「正练·完整」)、
技艺书(5 页,无总纲列,篇章格只写状态)、非书籍(只有耐久列)。
一次查多个目标时,按「页数 + 是否功法书」分组,各组行数取自实际命中。 - 列宽按显示宽度对齐(CJK/全角按 2 列计),终端与等宽字体下能对齐。
#181189是实例 Id(ItemKey.Id),用于定位具体哪一件,不是物品编号。- 功法书(
IsCombatSkillBook())多一个总纲列(承/合/解/异/独),且每篇带正练/逆练。
技艺书没有总纲、没有正逆,只有 5 篇。 持有者研读是角色侧状态(Character.IsBookRead(ItemKey),三档:已读完 / 在读 / 未读),
只有书籍组才有这一列;库房里的书没有持有者,写-。
篇章完整度是物品属性,读没读过是角色属性,两者不能互相推断。- 篇章完整度只有三档:完整 / 残缺 / 亡佚(研读效率 100% / 40% / 10%)。
- 筛选用
TAIWU_PAGE+TAIWU_PAGE_STATE。TAIWU_PAGE=3指的是第 3 个篇章,不是内部 pageId:
功法书内部 page0 是总纲,所以第 3 篇章对应 pageId=3;技艺书第 3 篇章对应 pageId=2。代码已代偿。 - 报"某篇完整"时,
item states里的计数是全存档该篇完整的件数(含无人持有的),
item detail/RESULT才是能实际拿到手的,两者不要混为一谈。 - 筛选后
hits只算通过筛选的持有者;没通过的不出现,这是预期行为。
能力边界:能查什么、不能查什么
能查(已实测)
| 类别 | 内容 | 数据来源 |
|---|---|---|
| 身份 | 姓名、charId、模板、性别、双性恋、跨性别、年龄 | _fullName _gender _bisexual _transgender _currAge |
| 位置 | 区域名 + areaId + 地块 BlockId | _location + Map._areas + Config.MapArea |
| 势力 | 门派/村落名、职位等级、聚落 ID、门派内影响力 | _organizationInfo + OrganizationDomain._sectCharacters |
| 六维 | 膂力/灵敏/定力/体质/根骨/悟性(当前值与基础值) | _currMainAttributes / _baseMainAttributes |
| 资质 | 14 项武学资质 + 16 项生活资质 | _baseCombatSkillQualifications / _baseLifeSkillQualifications |
| 功法 | 已学功法全表(名称+品级)、装备中功法 | _learnedCombatSkills + Config.CombatSkill |
| 生活技能 | 已学生活技能全表(名称+阅读页数) | _learnedLifeSkills + Config.LifeSkill |
| 特性 | 特性名全表 | _featureIds + Config.CharacterFeature(955条) |
| 装备 | 17 槽位物品名 | _equipment + 物品配置表 |
| 背包 | 物品名与数量(已按名称聚合) | _inventory.Items |
| 关系 | 夫妻/爱慕/好友/仇敌/血亲/师徒/结义,含好感度 | CharacterDomain._relations(84万条) |
| 状态 | 健康、内力、内力分配、名誉、声望、心情、伤病、中毒、入魔、武学兴趣 | _health _currNeili + Calc* |
| 物品分布 | 任意 TemplateId 的持有者与数量 | _inventory / _equipment |
| 售卖档位 | 该道具在哪些「商会 × 等级档」货架上、好感门槛、是否已解锁 | Config.Merchant(54 行) + MerchantType |
| 商队实持 | 哪支商队带着它、命中第几级 GoodsList、几件 |
MerchantDomain._caravanData(194) |
| 商行 / 商人 | 商行 5 家、临时商人货单记录(本存档 0 条) | BuildingDomain._shopManagerDict / MerchantDomain._merchantData |
| 商会好感度 | 七大商会 0~100 界面值 | MerchantDomain.GetCurFavorability(SByte) |
| 春日集市 | 当前开放的集市处数与 Adventure Id | AdventureDomain._adventures 中 TemplateId==77 |
| 仓库存放 | 该道具在太吾村私库/货仓、哪个定居点库房、外院/内院/禁院哪一档、几件 | TaiwuDomain._warehouse/_stock + OrganizationDomain.GetTreasury(id, layer) |
| 库房档位解锁 | 内院/禁院的支持度门槛与当前门派支持度 | Settlement.CalcApprovingRate() + GlobalConfig.TreasuryRquireApproving{Mid,High} |
| 耐久度 | 任意物品实例的 CurrDurability / MaxDurability |
ItemKey.Id → ItemDomain._* 实例 |
| 书籍篇章 | 每一篇章的完整度(完整/残缺/亡佚) | SkillBook._pageIncompleteState(每页 2bit) |
| 篇章心法 | 功法书每篇正练/逆练、总纲 承/合/解/异/独 | SkillBook._pageTypes(低 3 位总纲,bit3~7 各篇方向) |
| 按状态筛选 | 指定第 N 篇章 + 完整度 + 耐久下限,筛出符合条件的实例 | TAIWU_PAGE / TAIWU_PAGE_STATE / TAIWU_DUR_MIN |
| 派生属性 | 战力、魅力、生理年龄、健康上限、内力上限、名誉、声望 | CalcCombatPower 等 10 个 Calc*(world_2 实测 99912 / 900 / 24 / 781 / 1153 / -250 / 53) |
| 性格 | 五项性格维度 | CalcPersonalities(返回 Personalities 结构体的五个字段) |
| 造诣 | 武学造诣 CombatSkillShorts、生活造诣 LifeSkillShorts |
CalcCombatSkillAttainments / CalcLifeSkillAttainments |
| 地区恩义 | 25 个区域的当前恩义点数 | ExtraDomain._areaSpiritualDebt(Dictionary<AreaId,int>) |
| 库房恩义通道 | 内院/禁院可用恩义 400/700 替代支持度,二选一即解锁 | 同上 + GlobalConfig.TreasuryRquireSpiritualDebt{Mid,High} |
| 配货权重 | 每个货架的出货权重、出货概率、四季是否供货 | Config.Merchant.GoodsRate[14] / SeasonsGoodsRate0..3[14] |
| 研读进度 | 每个持有者对该书的研读状态(已读完 / 在读 / 未读) | Character.IsBookRead(ItemKey) / BookIsReadable(ItemKey) |
域 19 Extra / 域 20 Story |
两域已完整加载,字段可直接反射读取 | 取决于机制 2 的启动序列,见上 |
为什么这一整批都能查了:以上「派生属性 / 性格 / 造诣 / 域 19/20」原本全部不可用,根因不是各自缺数据,而是
钩子跑在 Main 之前、没有复刻游戏的启动序列,导致域 19 收尾时崩、域 20 随之不加载,Calc* 一路连带失败。
补齐机制 2 的 8 项后 load = OK,这些全都不再需要特殊处理。
不能查 / 只能近似(如实标注,不要猜)
| 项目 | 现状与原因 |
|---|---|
| 商队下个月刷新后的货单 | 能给出每个货架的出货权重、概率与季节可用性,但刷新是加权随机抽取,不能预言具体名单。只能报「配置允许」+「当前实际持有」 |
| 「读到第几页」的细粒度页号 | 输出列只到 已读完 / 在读 / 未读 三档。GetCombatSkillBookCurrReadingInfo 能取到逐页位图(LifeSkillItem.ReadingState,1bit/页),如需精确页号要另接一列 |
域 20 Story 的剧情行为 |
数据字段可读(如 _sectEmeiBreakBonusData 94 条),但剧情脚本运行时需要真实主线程 DataContext,TaiwuEventDomain.InitializeOnInitializeGameDataModule() 在钩子里必然抛 ArgumentNullException,这属于设计限制,不要试图修复 |
| 已启用 DLC 的专属内容 | 钩子把 DlcManager._dlcInfoList / _enabledDlcIds 置为空集合以绕过 NRE,DLC 专属表不参与查询 |
输出铁律:算不出来的一律写 N/A。若姓名出现 ?、地名出现 Name_123、数值全是 0,说明链路错了,不要当成真实数据汇报。
扩展新查询的通用套路
- 先探查:
TAIWU_MODE=probe打印各域加载状态与Character全部字段 - 确认容器:读档后
Count()一次目标容器,确认不是 0 - 看结构:取第一条 entry,打印 Key/Value 的类型和字段名
- 找语义:位标志/枚举要反射取
IsLiteral常量(Enum.GetValues对const class无效) - 小样本验证:先只输出 5~10 条,人工核对姓名地名是否合理,再全量
- 注意去重:关系类数据双向各存一条;背包里同一物品可能有多个
ItemKey(key 含实例 Id),必须按名称聚合
验证是否可信的硬标准:姓名、地名必须是可读中文。
参考文件
scripts/run_npc.sh— NPC 情报查询引导脚本scripts/NpcInspector.cs— NPC 情报钩子(summary / detail / probe 三模式)scripts/run_extract.sh— 物品查询引导脚本(持有者 + 售卖渠道 + 存放仓库 + 耐久/篇章)scripts/StartupHook.cs— 物品/货单/仓库/实例状态扫描钩子references/npc-fields.md— 角色字段全图、属性顺序、Calc 方法清单、可查/不可查references/archive-structure.md— 存档结构、域加载顺序、配置表机制、启动序列 8 步references/merchants.md— 商会货单 54 行全表、GoodsList 序号=等级档、好感度双表示、春日集市references/warehouses.md— 太吾村私库/货仓、定居点三层库房、档位枚举与支持度门槛、懒加载坑references/item-states.md— ItemKey→实例、耐久字段、篇章位掩码全解、正逆练与总纲、筛选参数references/reflection-api.md— 类型/字段/方法速查表references/localization.md— 语言表规则、姓名拆解、ID→名称映射references/relations.md— 关系位标志、性取向、去重与死亡过滤references/troubleshooting.md— 编译与运行错误对照表references/example-world2.md— 已实测的完整输出,作为回归基线