From 1f5ba9fc959154d441d6da9521d3300dc170c9e4 Mon Sep 17 00:00:00 2001 From: kitbyte Date: Fri, 15 May 2026 14:46:04 +0300 Subject: [PATCH] patch: fix a syntax error in the Disable Updates, websocket connection wouldn't automatically reconnect, ui rerender optimizations bump version to 1.0.8.1 --- CHANGELOG.md | 8 +++ README.md | 26 ++++++- WandEnhancer/Core/EnhancerConfig.cs | 2 +- WandEnhancer/Properties/AssemblyInfo.cs | 4 +- web-panel/src/app.tsx | 71 ++++++++++++++----- .../components/CategorySection.tsx | 37 +++++++--- .../remote-panel/components/CheatTile.tsx | 6 +- .../remote-panel/components/LibraryDrawer.tsx | 10 +-- web-panel/src/index.css | 10 +++ 9 files changed, 138 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b52ed79..acf2622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ This file is the source of truth for release notes. The newest entry must match the version in `WandEnhancer/Properties/AssemblyInfo.cs`. +## [1.0.8.1] - 2026-05-15 + +### Fixes + +- Fixed a syntax error in the Disable Updates patch that prevented Wand from launching. #70 +- Fixed an issue where the Remote Web Panel WebSocket connection wouldn't automatically reconnect when turning returning to the app or turning on the screen. +- Reduced battery consumption and device heating on mobile device by optimizing heavy UI blur effects and eliminating unnecessary React re-renders in the Remote Web Panel. #67 + ## [1.0.8.0] - 2026-05-06 ### Features diff --git a/README.md b/README.md index 32fff18..3b9c042 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,37 @@ WandEnhancer includes a built-in **Remote Web Panel** allowing you to control ap 3. Scan the displayed **QR code** with your phone's camera. ### Troubleshooting & Remote Access: -- **Page isn't loading?** First, ensure both your PC and phone are connected to the **exact same Wi-Fi network**. Next, make sure **Network Discovery** is turned on in your Windows network settings. If it still doesn't work, Windows Firewall might be blocking the connection—you may need to manually allow inbound traffic on TCP port `3223`. +- **Page isn't loading?** First, ensure both your PC and phone are connected to the **same local network**. Some routers and guest Wi-Fi networks enable client isolation/AP isolation, which blocks devices on the same SSID from reaching each other. If it still does not load, check Windows Firewall and allow inbound traffic on TCP port `3223` for your local network. If Windows marked your connection as **Public**, switching it to **Private** can also help. - **Using mobile data or a different network?** If you want to use the panel over mobile data (LTE/5G) or from an entirely different network, you can use [Tailscale](https://tailscale.com/) or similar VPN tools. ## 👀 How to use? 1. Go to the [Releases](https://github.com/k1tbyte/Wand-Enhancer/releases) page. -2. Download the latest source or binary. +2. Download the latest binary release. 3. Run the enhancer to apply local client modifications. +> Source archives are intended for developers who want to build the project locally. They are not prebuilt binaries. + +## 🛠️ How to build from source + +Building from source on Windows requires a local development environment. + +### Requirements + +- `CMake` +- `Node.js` and `pnpm` +- `Visual Studio 2022` or `Build Tools for Visual Studio 2022` with `MSBuild` +- Visual Studio `Desktop development with C++` workload +- .NET Framework 4.8 desktop build tools / targeting pack + +### Build steps + +1. Clone this repository. +2. Install the requirements above and make sure `cmake`, `pnpm`, and `MSBuild` are available. +3. Run `build.cmd` from Command Prompt or PowerShell. + +The build script installs the web panel dependencies, builds the frontend, compiles the native helper with CMake, restores NuGet packages, and builds the WPF solution. + --- ## ❓ Q&A diff --git a/WandEnhancer/Core/EnhancerConfig.cs b/WandEnhancer/Core/EnhancerConfig.cs index 3627496..ec15621 100644 --- a/WandEnhancer/Core/EnhancerConfig.cs +++ b/WandEnhancer/Core/EnhancerConfig.cs @@ -85,7 +85,7 @@ namespace WandEnhancer.Core SearchHints = new[] { "ACTION_CHECK_FOR_UPDATE" }, Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)", RegexOptions.Singleline), - Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))" + Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null))))" } } }, diff --git a/WandEnhancer/Properties/AssemblyInfo.cs b/WandEnhancer/Properties/AssemblyInfo.cs index 6ac6d30..0ee02ba 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.8.0")] -[assembly: AssemblyFileVersion("1.0.8.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.8.1")] +[assembly: AssemblyFileVersion("1.0.8.1")] \ No newline at end of file diff --git a/web-panel/src/app.tsx b/web-panel/src/app.tsx index 25ff404..b286065 100644 --- a/web-panel/src/app.tsx +++ b/web-panel/src/app.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useReducer, useRef, useState, type UIEvent } from 'react'; +import { useCallback, useEffect, useMemo, useReducer, useRef, useState, type UIEvent } from 'react'; import { buildPinnedGroup, filterGroups, groupCheatsByCategory } from '@/features/remote-panel/category'; import { CategorySection } from '@/features/remote-panel/components/CategorySection'; @@ -16,7 +16,7 @@ import { loadPinnedGameIds, savePinnedGameIds, togglePinnedGame } from '@/featur import { handleProtocolMessage } from '@/features/remote-panel/message-handler'; import { getPinnedStorageKey, loadPinnedTargets, savePinnedTargets } from '@/features/remote-panel/pinned-storage'; import { capturePresetValues, createPreset, getPresetStorageKey, loadPresets, savePresets, type RemotePreset } from '@/features/remote-panel/preset-storage'; -import { normalizeOutgoingValue, type CheatSchema, type InstalledAppSummary, type TrainerMetaPayload } from '@/features/remote-panel/protocol'; +import { normalizeOutgoingValue, type CheatSchema, type InstalledAppSummary } from '@/features/remote-panel/protocol'; import { ECheatType } from '@/features/remote-panel/protocol'; import { PanelSocketClient } from '@/features/remote-panel/socket-client'; import { createInitialPanelState, EConnectionStatus, panelReducer } from '@/features/remote-panel/state'; @@ -35,19 +35,27 @@ export const App = () => { const [presets, setPresets] = useState([]); const lastScrollRef = useRef(0); const clientRef = useRef(null); - const trainerMetaRef = useRef(state.trainerMeta); + const stateRef = useRef(state); + const handleConnectRef = useRef<() => void>(() => {}); + const pinnedStorageKeyRef = useRef(''); + const reconnectTimeoutRef = useRef(null); useEffect(() => { setPinnedGameIds(loadPinnedGameIds()); return () => { + if (reconnectTimeoutRef.current) { + window.clearTimeout(reconnectTimeoutRef.current); + } clientRef.current?.disconnect(); clientRef.current = null; }; }, []); useEffect(() => { - trainerMetaRef.current = state.trainerMeta; - }, [state.trainerMeta]); + stateRef.current = state; + handleConnectRef.current = handleConnect; + pinnedStorageKeyRef.current = pinnedStorageKey; + }); const activeTrainer = state.trainerMeta?.trainer ?? null; const libraryGames = useMemo( @@ -83,8 +91,22 @@ export const App = () => { } }, []); + useEffect(() => { + function onVisibilityChange() { + if (document.visibilityState === 'visible' && !clientRef.current?.isOpen()) { + handleConnectRef.current(); + } + } + document.addEventListener('visibilitychange', onVisibilityChange); + return () => document.removeEventListener('visibilitychange', onVisibilityChange); + }, []); + function handleConnect(): void { clientRef.current?.disconnect(); + if (reconnectTimeoutRef.current) { + window.clearTimeout(reconnectTimeoutRef.current); + reconnectTimeoutRef.current = null; + } const wsUrl = state.wsUrl.trim(); if (!wsUrl) { @@ -95,8 +117,17 @@ export const App = () => { const nextClient = new PanelSocketClient(wsUrl, { onConnecting: () => dispatch({ type: 'connecting' }), onOpen: () => dispatch({ type: 'connected' }), - onMessage: (message) => handleProtocolMessage(dispatch, message, trainerMetaRef.current), - onClose: () => dispatch({ type: 'error', message: 'The WebSocket connection closed.' }), + onMessage: (message) => handleProtocolMessage(dispatch, message, stateRef.current.trainerMeta), + onClose: () => { + dispatch({ type: 'error', message: 'The WebSocket connection closed.' }); + if (document.visibilityState === 'visible') { + reconnectTimeoutRef.current = window.setTimeout(() => { + if (document.visibilityState === 'visible' && stateRef.current.wsUrl.trim()) { + handleConnectRef.current(); + } + }, 2000); + } + }, onError: (message) => dispatch({ type: 'error', message }), }); @@ -105,30 +136,36 @@ export const App = () => { } function handleDisconnect(): void { + if (reconnectTimeoutRef.current) { + window.clearTimeout(reconnectTimeoutRef.current); + reconnectTimeoutRef.current = null; + } clientRef.current?.disconnect(); clientRef.current = null; dispatch({ type: 'disconnected' }); } - function handleCheatChange(cheat: CheatSchema, nextValue: unknown): void { + const handleCheatChange = useCallback((cheat: CheatSchema, nextValue: unknown): void => { + const { connectionStatus, trainerMeta } = stateRef.current; const normalizedValue = normalizeOutgoingValue(cheat, nextValue); dispatch({ type: 'setPending', target: cheat.target, pending: true }); dispatch({ type: 'valueChanged', target: cheat.target, value: normalizedValue }); - if (state.connectionStatus !== EConnectionStatus.Connected || !state.trainerMeta || !clientRef.current) { + if (connectionStatus !== EConnectionStatus.Connected || !trainerMeta || !clientRef.current) { dispatch({ type: 'setPending', target: cheat.target, pending: false }); return; } - const sent = clientRef.current.setValue(state.trainerMeta.trainer.trainerId, cheat.target, normalizedValue, cheat.uuid); + const sent = clientRef.current.setValue(trainerMeta.trainer.trainerId, cheat.target, normalizedValue, cheat.uuid); if (!sent) { dispatch({ type: 'setPending', target: cheat.target, pending: false }); dispatch({ type: 'error', message: 'The bridge socket is not open.' }); } - } + }, []); - function handleToggleCheatPin(cheat: CheatSchema): void { - const next = { ...state.pinnedTargets }; + const handleToggleCheatPin = useCallback((cheat: CheatSchema): void => { + const { pinnedTargets } = stateRef.current; + const next = { ...pinnedTargets }; if (next[cheat.target]) { delete next[cheat.target]; } else { @@ -136,8 +173,8 @@ export const App = () => { } dispatch({ type: 'togglePinnedTarget', target: cheat.target }); - savePinnedTargets(pinnedStorageKey, next); - } + savePinnedTargets(pinnedStorageKeyRef.current, next); + }, []); function handleToggleGamePin(game: LibraryGame): void { const next = togglePinnedGame(game, pinnedGameIds); @@ -248,11 +285,11 @@ export const App = () => {
-
+
setLeftOpen(true)} /> -
+
{!connected ? ( setLeftOpen(true)} /> ) : !activeTrainer ? ( diff --git a/web-panel/src/features/remote-panel/components/CategorySection.tsx b/web-panel/src/features/remote-panel/components/CategorySection.tsx index 5dd06dc..70f218a 100644 --- a/web-panel/src/features/remote-panel/components/CategorySection.tsx +++ b/web-panel/src/features/remote-panel/components/CategorySection.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import { Icon } from '@/components/ui/icon'; @@ -20,7 +20,7 @@ type CategorySectionProps = { onTogglePin: (cheat: CheatSchema) => void; }; -export const CategorySection = ({ +const CategorySectionBase = ({ group, values, pendingTargets, @@ -36,6 +36,15 @@ export const CategorySection = ({ const toggleCount = useMemo(() => getToggleCount(group.cheats), [group.cheats]); const handleToggle = () => setOpen((current) => !current); + const cheatHandlers = useMemo( + () => + group.cheats.map((cheat) => ({ + onChange: (nextValue: unknown) => onCheatChange(cheat, nextValue), + onTogglePin: () => onTogglePin(cheat), + })), + [group.cheats, onCheatChange, onTogglePin], + ); + useEffect(() => { if (forceOpen) { setOpen(true); @@ -45,17 +54,17 @@ export const CategorySection = ({ return (
-
+
{group.cheats.map((cheat, index) => ( onCheatChange(cheat, nextValue)} - onTogglePin={() => onTogglePin(cheat)} + onChange={cheatHandlers[index].onChange} + onTogglePin={cheatHandlers[index].onTogglePin} /> ))}
@@ -74,6 +83,18 @@ export const CategorySection = ({ ); }; +export const CategorySection = memo(CategorySectionBase, (prev, next) => { + if (prev.group !== next.group) return false; + if (prev.disabled !== next.disabled) return false; + if (prev.forceOpen !== next.forceOpen) return false; + for (const cheat of next.group.cheats) { + if (prev.values[cheat.target] !== next.values[cheat.target]) return false; + if (prev.pendingTargets[cheat.target] !== next.pendingTargets[cheat.target]) return false; + if (prev.pinnedTargets[cheat.target] !== next.pinnedTargets[cheat.target]) return false; + } + return true; +}); + function getEnabledToggleCount(cheats: CheatSchema[], values: Record): number { return cheats.filter((cheat) => cheat.type === ECheatType.Toggle && Boolean(values[cheat.target])).length; } diff --git a/web-panel/src/features/remote-panel/components/CheatTile.tsx b/web-panel/src/features/remote-panel/components/CheatTile.tsx index ec2890c..7795649 100644 --- a/web-panel/src/features/remote-panel/components/CheatTile.tsx +++ b/web-panel/src/features/remote-panel/components/CheatTile.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'; +import { memo, useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'; import { Icon } from '@/components/ui/icon'; import { cn } from '@/lib/utils'; @@ -23,7 +23,7 @@ const SWIPE_TRIGGER = 56; const SWIPE_DEAD_ZONE = 8; const SWIPE_ANIMATION_MS = 220; -export const CheatTile = ({ cheat, value, pending, disabled, pinned, first, onChange, onTogglePin }: CheatTileProps) => { +const CheatTileBase = ({ cheat, value, pending, disabled, pinned, first, onChange, onTogglePin }: CheatTileProps) => { const [offset, setOffset] = useState(0); const [animating, setAnimating] = useState(false); const [armed, setArmed] = useState(false); @@ -127,6 +127,8 @@ export const CheatTile = ({ cheat, value, pending, disabled, pinned, first, onCh ); }; +export const CheatTile = memo(CheatTileBase); + const PinReveal = ({ pinned, armed }: { pinned: boolean; armed: boolean }) => { return (
diff --git a/web-panel/src/features/remote-panel/components/LibraryDrawer.tsx b/web-panel/src/features/remote-panel/components/LibraryDrawer.tsx index 939e864..9371418 100644 --- a/web-panel/src/features/remote-panel/components/LibraryDrawer.tsx +++ b/web-panel/src/features/remote-panel/components/LibraryDrawer.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +import { memo, useMemo, type ReactNode } from 'react'; import { Icon, type IconName } from '@/components/ui/icon'; @@ -18,9 +18,9 @@ type LibraryDrawerProps = { onQueryChange: (query: string) => void; }; -export const LibraryDrawer = ({ games, query, canLaunch, onClose, onPin, onPlay, onStop, onQueryChange }: LibraryDrawerProps) => { - const filteredGames = filterLibraryGames(games, query); - const sections = getLibrarySections(filteredGames); +const LibraryDrawerBase = ({ games, query, canLaunch, onClose, onPin, onPlay, onStop, onQueryChange }: LibraryDrawerProps) => { + const filteredGames = useMemo(() => filterLibraryGames(games, query), [games, query]); + const sections = useMemo(() => getLibrarySections(filteredGames), [filteredGames]); return (
@@ -60,6 +60,8 @@ export const LibraryDrawer = ({ games, query, canLaunch, onClose, onPin, onPlay, ); }; +export const LibraryDrawer = memo(LibraryDrawerBase); + type GameSectionProps = { title: string; count?: number; diff --git a/web-panel/src/index.css b/web-panel/src/index.css index 377f8b1..ec7729f 100644 --- a/web-panel/src/index.css +++ b/web-panel/src/index.css @@ -166,6 +166,16 @@ -webkit-backdrop-filter: none; backdrop-filter: none; } + + .remote-glass-header { + -webkit-backdrop-filter: none; + backdrop-filter: none; + } + + .remote-glass-control { + -webkit-backdrop-filter: none; + backdrop-filter: none; + } } .remote-glass-control {