堆叠大师修复

创作者: s6652289
已更新:2026-09-19 14:18:54
25KB

前置 MOD

使用 堆叠大师修复 时将自动安装

关于此 MOD

Stackmaster 1.2.0 的两个仅客户端补丁:(A) 快速抓取材料从不会从箱子中扣除(复制问题);(B) 其新增的记忆箱子回退机制会重新填充你刚刚清空的箱子(上游有意为之,并非漏洞)——已禁用。

堆叠大师修复

适用性声明

中文本模组是给第三方模组 JStack424-堆叠大师 打出租车客户端补丁,含两个互相独立的修复项:

项 内容 性质
A 建造菜单 Alt + 左键(上游叫 快速拾取材料)拉取一整套材料时,箱子里的材料不扣减(等于复制) 是 bug(在上游 1.2.0 上依然存在)
B 上游 1.2.0 新增的「记住上次直接用过的箱子」回退:你把某个箱子清空后,在别处按 Alt + E(快速堆叠),刚取出来的东西会被原样塞回那个空箱 不是 bug,是上游有意做的特性(见下)

⚠️ 关于 B 项:这不是原模组的 bug,只是我个人的偏好开关

上游 1.2.0 的更新日志明确写着这是有意添加的功能 —— 「快速堆叠会记住每种确切物品类型最近一次直接观察到的箱子, 即使那个宝箱库存归零后”,目的是方便“补货”:你有个装木材的箱子,把它用空了,下次快速堆叠时木材会自动回到那个箱子。

我认为“被我清空的箱子就应该保持空着”,所以用 禁用记忆宝箱 这个开关把它关掉。这只是个人习惯,不代表上游做错了。如果你觉得上游的行为挺好,请不要使用本补丁,或者将该开关设为 假 即可原样恢复上游行为。

  • 这个 A 项 bug 是在我这台装了 30+ 模组、且以客机身份连远程专用服务器在环境中复现并定位的。 原模组作者与这些问题无关,有疑问请先找我,不要去找原作者。
  • 原模组在别的环境下可能并没有 A项这个bug,症状也可能是我这台的特定组合导致的; 不保证适用于你的环境。
  • 本补丁只做这两件事: A:把扣减从「只读快照副本」改到「真实容器」,并在事务成功后强制写回 ZDO; B:把上游那张「记忆箱子」回退表的返回值换成空表,让回退永不触发。 不改任何数值、不动上游的记忆存档数据、不加任何新功能。
  • 本补丁未经原作者同意或审阅,也不主张任何“原模组有bug”的结论(A项除外,那一项我给了IL证据)——整体上只是我这台机器上的绕行方案。
  • 若上游之后自己修好了 A 项,请优先升级上游模组并停用本补丁; 若你喜欢上游 B 项的行为,把 禁用记忆宝箱 设为 假 即可。

英语这是 客户端补丁 第三方模组 JStack424-栈主,与 两处独立修复:

  • A|快速采集材料未消耗材料 (Alt + 左键点击建筑部件):箱子从未被扣除,即物品复制。
  • B|快速堆叠会把物品放回你刚清空的箱子: 堆叠大师 1.2.0 引入 将“记住最后一次直接观察到的箱子”作为备用目的地。

⚠️ B不是原模组中的漏洞。 上游1.2.0更新日志指出此行为是 故意的 (即使那个箱子库存归零后,也方便补货)。这个补丁只是 关闭该功能,因为我个人更喜欢空箱子保持为空. 如果你喜欢上游行为,请不要使用此补丁,或者设置 禁用记住宝箱 = false 将其完全恢复。

  • 这 A 已在以下环境重现并诊断出该漏洞: 我的 设置:一个30多个模组的客户端正在连接到 远程专用服务器. 原模组作者对这一切概不负责。
  • 原模组在你的环境中可能完全正常;此补丁是 不保证适用于你.
  • 该补丁仅(A)将撤回操作从只读快照副本重定向到实时容器,并在事务成功后强制容器执行ZDO写入,(B)为记忆宝箱回退返回空映射。它不更改任何数值,从不触及存储的记忆数据,也不添加任何功能。
  • 此补丁为 未经原作者认可或审核,并且没有声称原模组有缺陷(除了物品A,对此提供了IL证据)——这是一个“在我机器上能运行”的变通方案。
  • 如果上游修复了物品A,请更新Stackmaster并 禁用此补丁. 如果你喜欢上游的物品B行为,请设置 禁用记住宝箱 = false.

🤖 本模组的代码由 AI 生成(AI 生成),需求、验证与发布由人类完成。


v1.2.0:新增「拦截记忆箱子回退」(B 项)

症状:把一个箱子里的东西取空 → 走到别的箱子按 Alt + E 快速堆叠 → 刚取出来的东西又被塞回那个空箱子。

根因(对 Stackmaster.dll 1.2.0 反汇编核对,316,416 B / sha256 be2fc8941b1abd96…):

  1. 上游 1.2.0 新增了 已记住的宝箱位置:只要你直接打开/对准过某个箱子,就按"物品种类"把它记进本地 玩家偏好设置(上限 512 条、180 天过期)。 箱子被清空也不会抹掉这条记忆 —— 这是 changelog 里明写的有意设计。
  2. 正常堆叠只进“当前已经装有同类物品”的箱子,不会主动填空箱: <>c__DisplayClass2_0::<PlanDeposits>b__1 的整个筛选逻辑就是 container.InitialCompatibilityKeys.Contains(source.CompatibilityKey) ⇒ 空箱子的候选键集合为空,永远进不了正常候选列表。
  3. 于是有了「回退」这条路(存储转移计划.计划存入,IL 0x00FF-0x014A): 当剩余量 > 0 且本次一件都没搬走且 记住的.尝试获取值(堆叠Id, 输出 容器Id) 命中,且该箱子在当前发现范围内 ⇒ 把这一份放进那个箱子。 ⇒ 回退只可能发生在“这个箱子当前一件同类物品都没有”的时候。

修改在 只读字典<string,string> Stackmaster.RememberedChestDestinations.ResolveFallbacks( 玩家, 物品栏快照, 发现结果) 上打一个 Postfix,把返回的 栈ID → 容器ID 回退表换成空表 ⇒ 存款计划 的 如果字典中存在该键,则返回该键对应的值 永远未命中 ⇒ 回退永不触发。

影响面为什么是封闭的(脚本/验证堆叠大师修复.py 每次交付前自动核这四条):

核对项 实测结论
解析回退 全 DLL 调用点 只有 2 个:开始存储操作 与 <FinishAfterOwnership>d__17.MoveNext —— 都是快速堆叠存料路径
记忆数据读取点 已记住目的地状态:尝试获取 只被 回退解析 调用 1 次 ⇒ 在返回值上拦截即完全封闭
是否影响合成扣料 / 快速取料(A 项) 不影响,那是另外的类(附近资源 / 快速抓取材料动作)
目标签名 3 个参数,返回类型恰好为 只读字典<字符串,字符串>,与 Postfix 的 参考 参数一字不差

刻意不做的事:

  • 不改上游的记忆存档(观察直接 / 玩家偏好设置 原样保留)。 ⇒ 本修复完全可逆把 禁用已记住的宝箱 设为 假 立刻恢复上游行为,不用重建记忆。
  • 不碰“失败堆叠标红框”那套(存款失败警告)。⚠️ 须知副作用:上游 1.2.0 会对「合格但最终留在背包里」的物品临时打 数量! 红框(悬浮该物品一次即清除)。本修复让这部分物品真的留在背包里 ⇒ 红框出现的机会比以前多一点。 这是上游既有的提示机制,不是本补丁引入的新行为 —— 在离家老远、附近没箱子时按 Alt+E 本来就会满屏红框。

配置:

键 默认 说明
禁用记忆宝箱 真 关闭「记忆箱子」回退。设置 假 = 完全恢复上游 1.2.0 行为。

新增配置节后需要重启游戏才会写出该节。


⚠️ v1.1.0:合成/升级那半已移除

Stackmaster 1.1.5 自己修好了「用箱子材料合成/升级不扣料」。 依据:

  • 1.1.5 更新日志第一条即声明该修复(……从每个已选中的自有 Container.GetInventory() 重新捕获,而不是从分离的 ZDO 库存快照中重新捕获……),并写明已在游戏内验证。
  • 源码层面,附近资源服务 在执行期改用活库存重建运行时堆栈(附近资源.cs:1288 取 容器.获取物品栏(),:1294-1313 用它重建 handle,并在扣减前用 ReferenceEquals(runtime.Inventory, runtime.Container.Container.GetInventory()) 做 fail-closed 身份校验(:1673-1681),另配精确扣减账本与逆序回滚。

⇒ 本补丁最初针对 执行并回滚 的转译器已退化为恒等替换, 故 1.1.0 连同 从仓库修复制作 配置项一起删除。

⚠️ 但 1.1.5 的修复只覆盖制作:Quick Grab Materials 那条路径没动(见下),所以本补丁保留那一半。 2026-09-25 对上游 1.2.0 复核:这条路径依旧没修 —— 执行原子操作 里仍是 playerInventory.MoveItemToThis(runtime.Inventory, …),而那个 运行时.物品栏 仍来自 捕获以供快速抓取 → 捕获 → 发现(…, resourceReadOnly: true) 的分离 ZDO 快照 ⇒ 本补丁依然必需,没有可剔除的功能。

依赖因此升到 JStack424-Stackmaster-1.2.0。 ⚠️ 上游 1.2.0 把内部类 远征套装动作 改名 快速抓取材料动作 (1.2.0 更新日志将用户可见的“存放/补货”动作改为 Quick Stack,而“Quick Grab Materials”这一名称保持不变——改动的是内部类名)。本补丁 1.1.1 已跟随改成先试新名,再回退旧名, 两代上游都能装上。1.1.0 在 1.2.0 上会整体静默失效(找不到类型,180 秒后报错),请勿使用。


它修的是什么(快速抓取材料路径)

堆叠大师为了「不拿所有权也能读箱子」,把容器的序列化库存从 ZDO 解进一个临时背包 (新背包(真)),命名为 容器句柄.资源库存,作者注释自述 从不主张所有权或修改实时容器库存。

制作路径的执行期已经换成活库存了,但 快速拾取材料 没有:

QuickGrabMaterialsAction.Finish() → TryPrepare → CaptureForQuickGrab(player, true, true) → NearbyResources.Capture → ContainerDiscovery.Discover(…, resourceReadOnly: true) → ContainerDiscovery.Inspect:refreshed = !resourceReadOnly && … ⇒ false inventory = refreshed ? container.GetInventory() : null ⇒ null → 于是只解 ZDO 字节到 new Inventory(true),塞进 handle.ResourceInventory → NearbyResources.cs:1157 AddInventory(…, handle.ResourceInventory, …) ← runtimeStack 指向副本 → QuickGrabMaterialsAction.cs:656 MoveItemToThis(runtime.Inventory, …) ← 从副本里搬走

(制作专用 尝试占领已拥有的星球 / 可变捕获 从未被它调用。)

行号按 1.2.0 的源码给出;1.1.5 的对应位置在 远征工具包动作.cs 里,内容一字不差 ——也就是说上游这次只是改了文件和类的名字,没改行为。

而 新背包(true) 的 临时背包 == true,游戏里 背包.已变更() 第一句就是 ldfld m_temoraryInventory; brfalse; ret ⇒ 永不发 已更改 ⇒ 不触发 容器.容器变化时 ⇒ 从不调用 容器.保存() ⇒ ZDO 里材料一点没少。 同时模组用 前 - 后 在同一个副本高出差值,必然判定成功 ⇒ 玩家白拿一套材料。

它是怎么修的

编译器只挂载一个目标方法:

目标方法 替换内容
堆叠大师.快速抓取材料动作::原子执行
(上游 1.1.5 及更早叫 远征工具包操作)
获取库存、获取物品
  • 获取物品栏 → 真实容器的活库存(获取容器库存)
  • 获取物品 → 活库存里同一槽位的那个物品对象 (必须成对替换:移至此地 内部的扣减带 背包中是否包含该物品 的引用包含检查,只换 Inventory 不换 Item 会直接扣不动)
  • 事务成功后:对本次确实重定向过、且确实是本机拥有者 的容器执行 容器.保存() + ZDO管理器.强制发送ZDO,确保写回权威数据

安全取向:拿不到安全写入权(不是所有者()、容器已卸载、反射句柄缺失)时,返回一个空库存,让模组的 执行原子操作 自己判定失败并取消本次操作。 宁可取消,也不放行复制。

配置

BepInEx/config/s6652289.StackmasterFix.cfg

项 默认 说明
修复探险工具包 真 修「建造菜单 Alt + 左键拉取一整套材料(上游叫 Quick Grab Materials)」的材料复制。
键名沿用旧版的 修复远征工具包(上游 1.2.0 之前管它叫 expedition kit),改名会让已有配置失效,故不改
详细 真 打印每次重定向与强制写回的细节

旧版遗留在 cfg 里的 从仓库修复制作 键已无用,可手动删除(不删也无副作用)。

怎么看它生效了

日志(BepInEx/LogOutput.log里搜 修复:

[修复] 已打补丁:QuickGrabMaterialsAction.ExecuteAtomic(transpiler + 写回 postfix) [修复] transpiler 生效:QuickGrabMaterialsAction.ExecuteAtomic(N 条指令)→ get_Inventory 替换 4 处,get_Item 替换 5 处 [修复] 扣减重定向 → 真实容器 $piece_chest_blackmetal(Clone) uid=… 活库存总数=369 [修复] 已强制写回 ZDO: $piece_chest_blackmetal(Clone) uid=… 库存=368 zdoRev=1067

关键判据:拉一次材料后,箱子真的少了对应材料 修好了。 (在 1.1.5 及更早的上游上,日志里的名字会是 执行原子远征套装动作 —— 同一个方法,只是上游改过类名。) 若看到 [修复] ★transpiler 未匹配到任何目标指令 ⇒ Stackmaster 版本变了,本补丁失效,请反馈。

安装 / 卸载

  • 安装:解压把 StackmasterFix.dll 放到 BepInEx/plugins/StackmasterFix/
  • 卸载:删掉该目录
  • 依赖:denikson-BepInExPack_Valheim + JStack424-堆叠大师(不需要 Jotunn、不联网、纯客户端)

源码

D:\WorkBuddy\StackmasterFix\(本地工作区)

已知边界

  • 本文只验证到「材料确实从真实容器扣除,并写回 ZDO」。联机双端一致性、专用服务器长时间运行、容器被他人同时打开等边界场景未做穷尽测试。
  • 上游若改变 运行时资源栈 / 执行原子操作 的结构,本补丁会打印 翻译器未匹配 并自然失效(不会造成新的破坏)。
  • 合成/升级不再属于本补丁的职责范围:那由上游 1.1.5 自行处理,本补丁不再提供任何兜底。

Valheim: 英灵神殿 的热门 MOD

探索适用于 Valheim: 英灵神殿 的最佳MOD,带来新功能、升级画面,以及令人兴奋的方式来改变您的游戏体验。

使用 Xmod 解锁 Valheim: 英灵神殿 的全部潜力 — 今天就探索这些顶级 MOD!