diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f602fda..22bd3d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,13 +10,13 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 22 cache: pnpm @@ -27,7 +27,7 @@ jobs: run: ./build.ps1 -Configuration Release - name: Upload unsigned executable - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: WandEnhancer-unsigned path: WandEnhancer/bin/Release/WandEnhancer.exe diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 12d5d67..c438744 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -12,7 +12,7 @@ jobs: mirror: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11a7556..8108b49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,15 +16,15 @@ jobs: RELEASE_VERSION: ${{ github.ref_name }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 with: version: 10 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 22 cache: pnpm diff --git a/.github/workflows/validate-release-metadata.yml b/.github/workflows/validate-release-metadata.yml index 9799acc..faf1cb1 100644 --- a/.github/workflows/validate-release-metadata.yml +++ b/.github/workflows/validate-release-metadata.yml @@ -11,7 +11,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Validate version and changelog sync shell: pwsh diff --git a/CHANGELOG.md b/CHANGELOG.md index 90fab29..d5ee1d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This file is the source of truth for release notes. The newest entry must match the version in `WandEnhancer/Properties/AssemblyInfo.cs`. +## [1.0.9.3] - 2026-07-04 + +### Fixes + +- Fixed the Remote Web Panel no longer applying on newer Wand builds and reporting "unsupported version". The remote bridge patches now resolve Wand's minified internal names dynamically instead of relying on hardcoded ones that broke on Wand updates. #118 #123 #124 #126 +- Fixed Pro reverting to Free (with random sign-outs and the return of ads and the time limit) after linking a phone with Wand's mobile activation code. That native pairing triggers a server-side sign-out on a patched client, so the patcher now disables it; use the built-in Remote Web Panel to control Wand from another device instead. #120 + ## [1.0.9.2] - 2026-06-28 ### Important diff --git a/README.md b/README.md index 529811a..1283f2f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This repository does not publish official compiled binaries. Build your own exec *Here how you do it:* - https://github.com/user-attachments/assets/b03eeed4-ceb8-45c0-b09c-2b64cf098c64 +https://github.com/user-attachments/assets/7966cabe-0aa6-424d-8c2f-981ad91e0f91 diff --git a/WandEnhancer/Core/EnhancerConfig.cs b/WandEnhancer/Core/EnhancerConfig.cs index 423f11e..ab23c49 100644 --- a/WandEnhancer/Core/EnhancerConfig.cs +++ b/WandEnhancer/Core/EnhancerConfig.cs @@ -167,6 +167,20 @@ namespace WandEnhancer.Core @"const (?\w+)=""ACTION_SET_ACCOUNT"";function (?\w+)\((?[^)]*)\)\{return\{\.\.\.(?\w+),account:(?\w+)\}\}", RegexOptions.Singleline), PatchFactory = BuildSetAccountReducerPatch + }, + new PatchEntry + { + // Wand's native "connect phone" pairing (POST /v3/auth/remote_code) + // triggers a server-side device handoff that deauthorizes this desktop + // session - the reported "entered the mobile activation key and got + // signed out" bug. Neutralize the code issuer so native pairing can + // never start. The injected remote panel is independent of this flow + // (IPC bridge, not Wand's Pusher pairing) and keeps working. The + // rejection is swallowed by the caller's try/catch (renders no code). + Name = "disableNativeRemotePairing", + SearchHints = new[] { "requestRemoteAuthCode", "/v3/auth/remote_code" }, + Target = new Regex(@"requestRemoteAuthCode\(\)\{return this\.#[\w$]+\.post\(""/v3/auth/remote_code""\)\}"), + Patch = "requestRemoteAuthCode(){return Promise.reject(new Error(\"wand-enhancer: native mobile pairing disabled\"))}" } } }, @@ -237,17 +251,28 @@ namespace WandEnhancer.Core }, new PatchEntry { + // Inject the bridge init + setHandler right after the method's opening + // brace; the rest of setCurrentTrainer is left untouched. Only `${trainer}` + // (active-trainer field) and `${remoteSource}` (value-source enum, taken + // via lookahead from the sole `e.source!==` site) vary between builds and + // are resolved from the match — nothing is hardcoded. Name = "remoteBridgeBindHandler", SearchHints = new[] { "client-state" }, - Target = new Regex(@"setCurrentTrainer\(e,t=null\)\{const s=e\?\.trainerId\|\|null,i=\(s\?e\?\.gameId:null\)\|\|null,n=\(s\?e\?\.supportedVersions:null\)\|\|\[];if\(s===this\.#ke&&t===this\.#Ee\)return;"), - Patch = "setCurrentTrainer(e,t=null){this.__wandRemoteBridge||(this.__wandRemoteBridge=(()=>{try{const r=globalThis.require||require;const{ipcRenderer:c}=r(\"electron\");try{c.invoke(\"wand-remote-url\").then((u=>{u&&(globalThis.__wandRemoteBridgeUrl=u)}))}catch(e){}const send=(ch,p)=>{try{return c.invoke(ch,p&&JSON.parse(JSON.stringify(p)))}catch(e){}};return{sync:(s)=>send(\"wand-remote-sync\",s),valueChanged:(s)=>send(\"wand-remote-value-changed\",s),setHandler:(h)=>{if(this.__wandRemoteBridgeBound)return;this.__wandRemoteBridgeBound=true;try{c.invoke(\"wand-remote-set-handler-bind\")}catch(e){}c.on(\"wand-remote-set-value\",(_e,req)=>{try{h(req)}catch(e){}})}}}catch(e){try{const r=globalThis.require||require,fs=r(\"node:fs\"),os=r(\"node:os\"),p=r(\"node:path\");fs.appendFileSync(p.join(os.tmpdir(),\"wand-remote-bridge.log\"),\"[\"+new Date().toISOString()+\"] [renderer-bind-error] \"+(e&&e.stack||e)+\"\\n\");}catch(_){}return null}})());this.__wandRemoteBridge?.setHandler((e=>{if(!this.#Ee||!e?.target)return!1;return this.#Ee.isActive()?this.#Ee.setValue(e.target,e.value,g.kL.Remote,e.cheatId):!1}));this.__wandRemoteTrainerInfo=e??null;const s=e?.trainerId||null,i=(s?e?.gameId:null)||null,n=(s?e?.supportedVersions:null)||[];if(s===this.#ke&&t===this.#Ee)return;" + Target = new Regex(@"(?setCurrentTrainer\(e,t=null\)\{)(?=const s=e\?\.trainerId\|\|null,i=\(s\?e\?\.gameId:null\)\|\|null,n=\(s\?e\?\.supportedVersions:null\)\|\|\[\];if\(s===this\.#[\w$]+&&t===this\.(?#[\w$]+)\)return;)(?=.*?e\.source!==(?[\w$]+\.[\w$]+\.Remote))", + RegexOptions.Singleline), + Patch = "${head}this.__wandRemoteBridge||(this.__wandRemoteBridge=(()=>{try{const r=globalThis.require||require;const{ipcRenderer:c}=r(\"electron\");try{c.invoke(\"wand-remote-url\").then((u=>{u&&(globalThis.__wandRemoteBridgeUrl=u)}))}catch(e){}const send=(ch,p)=>{try{return c.invoke(ch,p&&JSON.parse(JSON.stringify(p)))}catch(e){}};return{sync:(s)=>send(\"wand-remote-sync\",s),valueChanged:(s)=>send(\"wand-remote-value-changed\",s),setHandler:(h)=>{if(this.__wandRemoteBridgeBound)return;this.__wandRemoteBridgeBound=true;try{c.invoke(\"wand-remote-set-handler-bind\")}catch(e){}c.on(\"wand-remote-set-value\",(_e,req)=>{try{h(req)}catch(e){}})}}}catch(e){try{const r=globalThis.require||require,fs=r(\"node:fs\"),os=r(\"node:os\"),p=r(\"node:path\");fs.appendFileSync(p.join(os.tmpdir(),\"wand-remote-bridge.log\"),\"[\"+new Date().toISOString()+\"] [renderer-bind-error] \"+(e&&e.stack||e)+\"\\n\");}catch(_){}return null}})());this.__wandRemoteBridge?.setHandler((e=>{if(!this.${trainer}||!e?.target)return!1;return this.${trainer}.isActive()?this.${trainer}.setValue(e.target,e.value,${remoteSource},e.cheatId):!1}));this.__wandRemoteTrainerInfo=e??null;" }, new PatchEntry { + // Pure insertion: splice one `valueChanged` bridge call in after the + // existing `client-value-changed` send, before the onValueSet callback + // closes. Resolves no private names — `${head}`/`${tail}` carry the + // original text verbatim. trainerId is omitted from the payload; + // bridge-state falls back to the active snapshot trainer. Name = "remoteBridgeValueDelta", SearchHints = new[] { "client-value-changed" }, - Target = new Regex(@"#ct\(e,t\)\{t\.push\(e\.onValueSet\(e=>\{this\.status===i\.Connected&&e\.source!==g\.kL\.Remote&&this\.#Me\?\.send\(""client-value-changed"",\{instanceId:this\.#Pe,name:e\.name,value:e\.value,cheatId:e\.cheatId\}\)\}\)\),this\.#Be\(\)\}"), - Patch = "#ct(e,t){t.push(e.onValueSet(e=>{this.status===i.Connected&&e.source!==g.kL.Remote&&this.#Me?.send(\"client-value-changed\",{instanceId:this.#Pe,name:e.name,value:e.value,cheatId:e.cheatId}),this.__wandRemoteBridge?.valueChanged({trainerId:this.#ke,target:e.name,value:e.value,oldValue:e.oldValue,source:String(e.source??\"desktop\"),cheatId:e.cheatId})})),this.#Be()}" + Target = new Regex(@"(?#[\w$]+\(e,t\)\{t\.push\(e\.onValueSet\(e=>\{this\.status===[\w$]+\.Connected&&e\.source!==[\w$]+\.[\w$]+\.Remote&&this\.#[\w$]+\?\.send\(""client-value-changed"",\{instanceId:this\.#[\w$]+,name:e\.name,value:e\.value,cheatId:e\.cheatId\}\))(?\}\)\),this\.#[\w$]+\(\)\})"), + Patch = "${head},this.__wandRemoteBridge?.valueChanged({target:e.name,value:e.value,oldValue:e.oldValue,source:String(e.source??\"desktop\"),cheatId:e.cheatId})${tail}" } } } diff --git a/WandEnhancer/Properties/AssemblyInfo.cs b/WandEnhancer/Properties/AssemblyInfo.cs index 67ac75d..24f1662 100644 --- a/WandEnhancer/Properties/AssemblyInfo.cs +++ b/WandEnhancer/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.9.2")] -[assembly: AssemblyFileVersion("1.0.9.2")] +[assembly: AssemblyVersion("1.0.9.3")] +[assembly: AssemblyFileVersion("1.0.9.3")] diff --git a/assets/fork-guide.mp4 b/assets/fork-guide.mp4 deleted file mode 100644 index 087fba9..0000000 Binary files a/assets/fork-guide.mp4 and /dev/null differ diff --git a/assets/usage-guide.mp4 b/assets/usage-guide.mp4 new file mode 100644 index 0000000..d35d368 Binary files /dev/null and b/assets/usage-guide.mp4 differ