Skip to content

Add TicketAuthority for reliable ownership-ticket answering + diversion startup change - #3

Closed
xib031 wants to merge 2 commits into
madoiscool:mainfrom
xib031:feat/ticket-authority
Closed

xib031 wants to merge 2 commits into
madoiscool:mainfrom
xib031:feat/ticket-authority

Conversation

@xib031

@xib031 xib031 commented Oct 13, 2026

Copy link
Copy Markdown

Adds TicketAuthority so the client answers ownership-ticket requests reliably,
and ports the diversion startup approach with startup hardening.

Why

When a DRM-wrapped game launches, Steam asks the client for the app ownership
ticket (IClientUser::GetAppOwnershipTicketExtendedData). The old handler
answered only when one cached blob existed (ConfigStore apptickets\7), and
otherwise returned without writing a reply. The DRM then aborts the launch,
which is the failure users report as error 54. Stored tickets were also served
unvalidated, and the SteamID spoof was decided independently of the bytes
served.

What this changes

TicketAuthority (src/Utils/Tickets/TicketAuthority.{h,cpp}) is the single
owner of the decision:

  • validated stored tickets are preferred; invalid ones fall through to
    forging and are kept only as a last-resort rung
  • the expected identity is derived once (stored ticket, stored SteamID
    credential, imports, or ETicket) and the SteamID spoof reads the same
    decision, so bytes and identity cannot disagree
  • forge bases are discovered: user hints, Lua ids, installed apps
    (appmanifest_*.acf + libraryfolders.vdf), per-app credentials, and the
    Steam client ticket last; every discovered id is probed in both storage
    locations
  • a private vault under /opensteamtool/vault keeps every served,
    base, imported, or backend blob, so cache clears and reinstalls cannot
    empty the machine
  • best-effort is the default: a launch is still answered while any usable
    signed bytes exist, and mixing is recorded as mixed_identity; setting
    identity_mode = "strict" restores refusal-only behavior
  • optional backend fallback, import validation for setAppTicket and
    setETicket, and a per-app report in opensteamtool/tickets.json

Startup:

  • steamclient64.dll is copied to bin\diversion64.dll and the copy is hooked,
    so the original module stays untouched in memory; module lookups and
    SteamUI's LoadModuleWithPath are redirected to the copy, with an automatic
    fallback to the original on any failure
  • the metadata cache is checked before the network, cache writes are atomic,
    and CloudRedirect lookup and shutdown paths are fixed

Configuration keys added under [tickets] in opensteamtool.toml:
prefer_stored, allow_forge, require_appid_match, require_account_match,
scan_credential_store, identity_mode, vault, allow_backend, report, sources.

Verification

  • built Release and Debug from this branch; the exported surface is unchanged
    (single TokeerUri export)
  • the build was exercised on a live Steam client: ownership tickets are
    answered and reported as expected

Notes

  • the branch is two commits: tickets, then startup
  • best-effort default is deliberate: an empty reply guarantees a launch
    failure, while mixed bytes may still launch; strict mode is available for
    anyone who prefers a clean refusal

The ticket handler used to answer only when one cached blob existed
(ConfigStore apptickets\7) and otherwise returned without a reply; the
DRM then aborts the launch. Stored tickets were also served unvalidated,
and the SteamID spoof was decided independently of the bytes served.

TicketAuthority now owns the decision:
- validate stored tickets before serving (embedded App ID, size, signature)
- derive the expected identity once; the spoof reads the same decision
- discover forge bases instead of a fixed list: user hints, Lua ids,
  installed apps, per-app credentials, and the client ticket last
- probe every discovered id in both storage locations
- keep a private vault so cache clears and reinstalls cannot empty it
- best-effort default that records mixed_identity; strict mode optional
- optional backend fallback and import validation at load time
- report each decision to opensteamtool/tickets.json
- copy steamclient64.dll to bin\diversion64.dll and hook the copy so the
  original module stays untouched in memory
- redirect module lookups and SteamUI's LoadModuleWithPath to the copy,
  with an automatic fallback to the original module on any failure
- pin the module and skip cleanup during process termination
- check the local metadata cache before the network and write it atomically
- fix CloudRedirect library lookup and shutdown bookkeeping

The diversion approach follows upstream OpenSteamTool PR #193 (BSD-3-Clause).
@xib031 xib031 closed this Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant