INFO ./docs/*
This repository patches the Wand Electron app from a .NET Framework WPF desktop tool. Keep changes narrow and preserve the patch pipeline invariants.
- 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 rewrites three account-returning service methods to injectsubscription:{period:"yearly",state:"active"}into the response before it reaches the store:getUserAccountandsetAccountWandBrandExperience(Resolver-style, service field via<service_name>placeholder) andsetAccountLanguage(BuildSetAccountLanguagePatchPatchFactory — captures the real param names + the originalpost("/v3/account/language",{...})expr and wraps.then). 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. If a future Wand build changes these method bodies, re-derive the regexes against the liveapp-*.bundle.js(do NOT trust.source/new— it is a different version).
- 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.
- 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.