mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-30 12:01:20 +00:00
f798714f8d
Add verify-dist.mjs (node --check on bundles, dev-only payload guard). Wire lint into build.ps1 and type-check both web and bridge in pnpm build. Drop noImplicitAny override and switch bridge to Bundler resolution. Run CI on pull_request and push to master.
11 KiB
11 KiB
INFO ./docs/*
Wand Enhancer Agent Notes
This repository patches the Wand Electron app from a .NET Framework WPF desktop tool. Keep changes narrow and preserve the patch pipeline invariants.
Remote Web Panel
- The default local remote port is
3223. Keep bridge and frontend constants aligned; C# must not duplicate the presentation URL or port. - The embedded panel must stay small because the desktop patcher embeds it and then injects it into Wand's
app.asar. - Remote tooltip links and every rendered
remote-qr-codeare redirected byweb-panel/bridge/scripts/default/remote-popup-cleanup.js. It reuses Wand's loaded QR renderer through the webpack runtime, keeps the local URL visible as a fallback, and hides the Pro onboarding remote mobile app card. Do not reintroduce C# ASAR patches for the tooltip URL or QR component; a changed UI bundle must not make the whole remote-panel patch fail. - Production builds must not include mock data, debug routes, sourcemaps, local fonts, heavy icon libraries, or runtime class helper packages.
- The Electron bridge is authored as TypeScript under
web-panel/bridge/src/, but production runtime must be bundled/minified intoweb-panel/dist/bridge.cjsbypnpm run build:bridge. Do not copy bridge source into Wand or embed it as ASAR resources. - Mock/demo data is dev-only and must be reached through
import.meta.env.DEVdynamic imports. - Source can use React-compatible imports, but production runtime resolves them to Preact aliases in
web-panel/vite.config.ts. - UI uses Tailwind CSS and lightweight local primitives under
web-panel/src/shared/ui/. - Default renderer script sources live in
web-panel/bridge/scripts/default/and are bundled/minified intoweb-panel/dist/renderer-scripts/bypnpm run build:bridge. Custom user scripts are selected in the WPF patch modal and copied fromPatchConfig.CustomScriptPaths; only existing.jsfiles are accepted. A localrenderer-scripts/folder next to the patcher exe is still copied as an advanced fallback. web-panel/bridge/scripts/default/installed-apps-sync.jsresolves Wand's renderer services/store and publishesMy Gamessnapshots through thewand-remote-installed-appsIPC channel. The synced list must mirror Wand'smy_gamessource criteria: catalog games come frominstalledGameVersions, and extra installed unsupported titles come fromcorrelatedUnavailableTitleswhosegames[].correlationIdsmatchinstalledApps.- If the injected renderer cannot read a populated
correlatedUnavailableTitlesslice from the live store,installed-apps-sync.jsmust fall back to Wand's/v3/unavailable_titlescorrelation lookup through the renderer API client instead of degrading to raw install entries or an emptyMy Gameslist. - Installed app snapshots should include game artwork in
imageUrlwhen possible.installed-apps-sync.jsmust prefer Wand's own client icon CDN shapehttps://api-cdn.wemod.com/steam_community/<steamAppId>/client_icon/96.webpwhenever the matched title/game/version metadata contains a Steam AppID, regardless of install platform. Do not assumesteamAppIdis a flat property; search nestedsteam*metadata before falling back to installed Steamsku. If metadata still does not expose the icon, fall back to the rendered Wand sidebar DOM (.sidebar-game-row-imagebackground-image) keyed bytitleIdparsed fromdata-tooltip-trigger-for. The web panelGameCovermust tolerate broken artwork URLs and fall back to its text cover. - The same renderer sync script also forwards lifecycle state through
wand-remote-game-status:game-launched/game-endedcome from Wand's launch monitor service, and trainer runtime comes from the running-trainer visibility service. The web panel consumes this as thegame_statuswebsocket message. - When Wand does not emit a
game-launchedevent but a trainer is already active,wand-remote-game-statusmust synthesize a running session from the running-trainer visibility payload so the remote panel does not show an idle game session next to a running trainer. - The websocket
hellosnapshot must still send cachedinstalled_appsandgame_statuseven when no trainer snapshot is active yet; do not reintroduce a handshake path that returns early aftertrainer_changed. - Remote Play/Stop uses the websocket
remote_commandmessage. The bridge forwards it overwand-remote-command/wand-remote-command-response, andinstalled-apps-sync.jsresolves Wand's trainer API + trainer service to launch a trainer for agameIdor end the current trainer. - Remote Play must construct Wand's real trainer launch request class (
69482.vO) before callingtrainerService.launch(...). Passing a plain object launches the game process but breaks Wand'sgetMetadata(vO)-based trainer state, causing missing status, disappearing play/close buttons, and stuck loading behavior. - Pro activation is a C# asar patch (
EPatchType.ActivatePro, independent of the remote panel / bridge). It wraps the returned promise of three account-returning service methods sosubscription:{period:"yearly",state:"active"}is injected before the response reaches the store:getUserAccount,setAccountWandBrandExperienceandsetAccountLanguage. A fourth patch (setAccountReducer) rewrites theACTION_SET_ACCOUNTstore reducer so any account write (periodicrefreshAccount, push/profile updates, etc.) keeps Pro even when it bypasses those API methods. Pro isam(account) = !!account.subscription(flags/512 are irrelevant).setAccountLanguageis the one the original two patches missed, which is why Pro dropped on language change.setAccountWandBrandExperiencedoes not exist on every build, so it is declared optional throughCapabilityHints.
Patch Engine
- Patches are located structurally, not by shape. A patch anchors on something Wand does not rename between builds — an API endpoint, an IPC channel name, a public method name — and then walks the delimiter structure (
Core/Js/JsCursor.cs) to the edit site. Identifiers that do change (#Xe,l.vO, the numeric Remote source) are read out of the located region, never baked into a pattern. A rebuild that only reminifies therefore needs no change here. - Never write a regex that spans a whole method body or matches across a bundle. Scope patterns to a located
JsFunctionviaResolve, where they run against a few hundred characters instead of megabytes. - A patch is one
PatchEntryinCore/EnhancerConfig.cswith aLocatedelegate returning the edits to splice. Returnnullwhen the anchor is absent from this file — that means "not my file", not "failure". Throw only when the anchor IS present but the surrounding structure is unrecognisable; that is a genuinely unsupported build and must fail loudly. - Injected JavaScript lives in
WandEnhancer/Patches/*.jsand is embedded aspatches/<name>.js. Load it withPatchPayload.Load(name, "key", value, ...), which fills${key}placeholders in one pass. Do not put payload JS back into C# string literals. - Multiple edits from one patch are applied highest-offset-first, so their positions stay valid. Keep them non-overlapping.
- A patch that only exists on some builds sets
CapabilityHints: absent capability logs a skip, a detected-but-unpatchable capability still fails the run. - When a build really does restructure something, add a fallback branch inside that patch's
Locaterather than a version table — old shapes keep working because the old branch is still there. - Verify against real bundles, minified and prettified, before shipping: locating must succeed on both and the patched files must pass
node --check.
ASAR Patch Pipeline
- Preserve and restore both
resources/app.asarandresources/app.asar.unpackedbackups. - Inject
web-panel/distasremote-panel/; it must already containbridge.cjsand generated default renderer scripts underrenderer-scripts/. Selected/local custom renderer scripts are then copied underremote-panel/renderer-scripts. - Do not commit extracted
.source/or.sources/output. Recreate it only for reverse-engineering sessions. AsarSharp.AsarExtractor.ExtractAllmust skip unpacked entries when their source path equals the destination (in-place extraction is a self-copy that fails on locked files likeTrainerLib_x64.dll) and silently skip unpacked entries whose source is missing on disk (e.g.auxiliary/GameLauncher.exeremoved by an installer). Do not reintroduce hard failure on either case.- The
DevToolsOnF12patch anchors on the Electron main-process<app>.whenReady().then(site and attaches abefore-input-eventhook to everyBrowserWindow.webContents. Do not patch the renderer keydown listener — the minifiedACTION_OPEN_DEV_TOOLSdispatch site is not stable across Wand releases. - Cheats can be pinned per game in the web panel via
pinned-storage.ts(localStoragekeywand-remote.pinned-cheats.v1:<gameId>). Pinned cheats render as a virtualpinnedcategory at the top of the list; their normal category placement is preserved. - Custom quick presets are per trainer/game and stored by
preset-storage.tsunderlocalStoragekeywand-remote.presets.v1:<gameId-or-trainerId>. Presets capture persistent cheat values only; do not includebuttonone-shot cheats in saved presets. - All
localStorageaccess inweb-panel/src/MUST go throughweb-panel/src/shared/storage.ts(loadJson/saveJson/loadStringSet/saveStringSet). Do not reintroduce per-capabilitytry/catch+JSON.parseduplication. Trainer/game storage IDs are derived through the sharedgetTrainerStorageId(trainer)helper; do not re-implement thegameId → titleId → trainerId → 'global'precedence inline. - Shared web protocol version, port, and HTTP/WS paths live in
web-panel/protocol/web-contract.json. Bridge-only IPC channels, WS opcodes, and renderer injection delays live inweb-panel/bridge/src/constants.ts. Do not redeclare these values inline. - UI string-union types follow the
E*enum convention from.claude/rules/frontend-conventions.md(currentlyECheatTypeinprotocol/messages.ts,EConnectionStatusinremote-session/remote-session.reducer.ts); wire string values must remain on the right-hand side of enum members. Reducer action tags stay as discriminated-union string literals. - Cheat input controls live one-per-file under
web-panel/src/trainer/controls/; sharedSliderTrack/StepButton/ControlInternalPropsare incontrols/shared.tsxand number formatting helpers incontrols/format-number.ts.controls/CheatControl.tsxremains a thin dispatcher keyed byECheatType. - Mobile drawer performance is sensitive to
backdrop-filter. Keep drawer panels and nested glass controls blur-free under coarse pointers, and do not add per-rowbackdrop-blur-*inside drawer lists.
Validation
- Web panel build:
cd web-panel && pnpm run build(runs type-check, Vite build, thenbuild:bridgeintodist). - Bridge/script syntax checks after build:
node --check web-panel/dist/bridge.cjsandnode --check web-panel/dist/renderer-scripts/remote-popup-cleanup.js. - Production dist should contain only static assets and should not contain
mock-instance,Mock Adventure,Simulation,Debug session,mock=1,demo-session,vite.svg,tailwind-merge,class-variance-authority, orclsx.