A .patch file does not carry enough for an installer to run the tier safely. The installer additionally needs to know which patches form one all-or-nothing unit (the two vendor-sale patches are useless apart), which companion .cs may only be copied once that unit has landed, whether the change needs a core solution rebuild or just ServUO's dynamic script build, and what capability an operator loses by declining. None of that is derivable from the diffs. patches/tier.json declares it, and release.yml folds it into manifest.json as `patch_tier` — so a new or changed patch regenerates release metadata rather than requiring an installer release, which is the same rule §7.1 already applies to the bundle. The staged copy is removed from patches/ so the tarball carries exactly one statement of the table. The release gate now checks the table in both directions: every .patch described by exactly one feature, every named patch and companion present, every declared target equal to the file the diff actually edits, and every rebuild kind one the installer understands. All four were previously invisible until someone ran the tier on a live shard. Refs: docs/installer/PLAN.md §2.2, §7.0 Co-Authored-By: Claude <noreply@anthropic.com>
5.7 KiB
patches
Unified diffs against stock ServUO 57.4 for files the bridge must modify rather than add. Anything that can be shipped as a whole file belongs in overlay/ instead.
Apply from the server root:
git apply --check patches/<name>.patch # dry run
git apply patches/<name>.patch
tier.json — adding or changing a patch
A .patch file does not say enough on its own. The Runic Gateway installer's patch tier also has to know which patches form one all-or-nothing unit, which companion .cs may only be copied once that unit has landed, whether the change needs a core solution rebuild or just the dynamic script build, and what the operator loses by declining. None of that is derivable from a diff, so it is declared in tier.json.
Adding a patch means adding it there in the same PR. The release workflow checks the table in both directions — every .patch described by exactly one feature, every named patch and companion present, every target equal to the file the diff actually edits — so a patch without an entry fails the release rather than shipping a tier that silently never offers it.
tier.json is folded into the tarball's manifest.json as patch_tier and removed from the staged patches/ directory, so the artifact carries exactly one copy of the table and it is the one the installer reads. Installers older than this key ignore it; an installer newer than the overlay it is deploying falls back to a built-in copy. See docs/installer/PLAN.md §2.2 and §7.0.
Phase 7 — player-vendor sale (a coupled unit)
Player-vendor purchases raise no EventSink. ValidVendorPurchase / ValidVendorSell cover NPC vendors only. The commit point is PlayerVendorBuyGump.OnResponse, the only place where buyer, vendor owner, price, and commission are all in scope — exactly what cheat detection needs. See PLAN.md §6.
This is the one non-drop-in piece. Apply all three together:
| Item | Target | What |
|---|---|---|
playervendor-sale-eventsink.patch |
Server/EventSink.cs |
Adds the PlayerVendorSale delegate, PlayerVendorSaleEventArgs { Buyer, Vendor, Owner, Item, Price, Commission }, the event field, and InvokePlayerVendorSale. |
playervendor-sale-gump.patch |
Scripts/Gumps/PlayerVendorGumps.cs |
One InvokePlayerVendorSale(...) call right after the committed HoldGold +=. |
BridgeVendorSale.cs |
copy to Scripts/Custom/Bridge/ |
The subscriber that emits vendor.sale. Not in overlay/ because it references PlayerVendorSaleEventArgs, which does not exist until the EventSink patch is applied — shipping it in overlay would break the build on any unpatched install. |
cd <servuo root>
git apply --check patches/playervendor-sale-eventsink.patch patches/playervendor-sale-gump.patch # dry run
git apply patches/playervendor-sale-eventsink.patch patches/playervendor-sale-gump.patch
cp patches/BridgeVendorSale.cs Scripts/Custom/Bridge/BridgeVendorSale.cs
Both patches are git-format and verified with git apply --check against stock ServUO 57.4. Modifying EventSink.cs means the core rebuilds, so ScriptCompiler's dynamic script build is not enough — rebuild the solution (dotnet build ServUO.sln) or the server binary.
Not applicable to a non-git shard? git apply works in a plain directory too. If patch is used instead, note the core files are CRLF; use patch --binary.
In-game moderation audit (admin controls §5.5)
So the website's moderation log stays complete, in-game uses of the write-plane verbs are forwarded to it as admin.audit (origin:"in-game"). Broadcasts already surface through EventSink.Command, but resolved bans/kicks only carry their target inside the command's own CommandLogging.WriteLine call — which has no event to subscribe to. One small change fixes that:
| Item | Target | What |
|---|---|---|
commandlogging-event.patch |
Scripts/Commands/Logging.cs |
Adds a public static event Action<Mobile,string> OnWrite, raised in WriteLine before the m_Enabled guard so it fires even when file logging is off. |
BridgeModerationAudit.cs |
copy to Scripts/Custom/Bridge/ |
The subscriber: taps OnWrite for ban/kick (parsing the target out of the log line) and EventSink.Command for [bcast, emitting admin.audit. Not in overlay/ because it references CommandLogging.OnWrite, which does not exist until the patch is applied. |
cd <servuo root>
git apply --check patches/commandlogging-event.patch # dry run
git apply patches/commandlogging-event.patch
cp patches/BridgeModerationAudit.cs Scripts/Custom/Bridge/BridgeModerationAudit.cs
Logging.cs is a Scripts file, so this is picked up by the dynamic script build — no core/solution rebuild needed (unlike the Phase 7 EventSink.cs patch). Verified end-to-end with tools/scaffolding/BridgeAuditProbe.cs (gated by Bridge.AuditProbeOnStart): a genuine [bcast plus simulated ban/kick log lines produced the expected admin.audit frames, target parsed, with non-moderation lines ignored.
Note on Scripts.csproj
Phase 0 modifies an existing file but ships as a whole-file overlay (overlay/Scripts/Scripts.csproj) because the file is small, we own it operationally, and a copy is less fragile than a diff against a project file. Revisit if it starts drifting from upstream.
Note on shard repairs
The deletions and edits described in SHARD_PREREQS.md are one-time repairs to a specific broken install, not part of the bridge. They are not shipped here.