mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-29 06:01:14 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b776c52fc | |||
| 9c88caf49b | |||
| 537608c381 | |||
| b0279ee812 | |||
| c007e11cce | |||
| a7f0eae670 | |||
| 1c9a8fb780 | |||
| ec07dc63f0 | |||
| 88556ec70f |
@@ -0,0 +1,8 @@
|
||||
patreon: kitbyte
|
||||
|
||||
custom: [
|
||||
"https://www.patreon.com/kitbyte/gift",
|
||||
"https://tronscan.org/#/address/TQdvau8pAy5Tg1Aa588tTcPCFgbcHtuoxc",
|
||||
"https://www.blockchain.com/explorer/addresses/btc/1EZKDcyU8REm9JW5xwXJqSpn5Xaq5yAWWX",
|
||||
"https://etherscan.io/address/0xd904d9d0557f88bbb1c4ab3582b4ca0d8a730e8d"
|
||||
]
|
||||
@@ -8,16 +8,16 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**🚨 STOP BEFORE YOU POST: THIS PROJECT HAS NO OFFICIAL YOUTUBE TUTORIALS. 🚨**
|
||||
If you downloaded an executable from a YouTube video link, you downloaded a virus/password stealer from a scammer. **Run an antivirus immediately and change your passwords.** Do not open issues about stolen accounts here — this project is not related to those videos.
|
||||
**STOP BEFORE YOU POST: THIS PROJECT DOES NOT PUBLISH OFFICIAL EXECUTABLE DOWNLOADS.**
|
||||
Build WandEnhancer yourself from your own fork or local source. If you downloaded an executable from YouTube, Discord, a mirror, or any other third-party website, treat it as untrusted. Do not open issues about third-party binaries or stolen accounts here - this project is not related to those downloads.
|
||||
|
||||
- type: checkboxes
|
||||
id: scam_check
|
||||
id: build_source_check
|
||||
attributes:
|
||||
label: ⚠️ Download Source Confirmation (REQUIRED)
|
||||
label: Build Source Confirmation (REQUIRED)
|
||||
description: You must check this box to proceed.
|
||||
options:
|
||||
- label: I confirm that I downloaded this tool DIRECTLY from this official GitHub repository, and NOT from a YouTube video, Discord, or any other third-party website.
|
||||
- label: I confirm that I built WandEnhancer myself from my own fork or local source, and did not download an executable from YouTube, Discord, mirrors, issue comments, or any other third-party website.
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
@@ -67,4 +67,4 @@ body:
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: Screenshots & Additional context
|
||||
description: Drag and drop screenshots here, or add any other context about the problem.
|
||||
description: Drag and drop screenshots here, or add any other context about the problem.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Build executable
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v5
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: web-panel/pnpm-lock.yaml
|
||||
|
||||
- name: Build unsigned executable
|
||||
shell: pwsh
|
||||
run: ./build.ps1 -Configuration Release
|
||||
|
||||
- name: Upload unsigned executable
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: WandEnhancer-unsigned
|
||||
path: WandEnhancer/bin/Release/WandEnhancer.exe
|
||||
if-no-files-found: error
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +48,5 @@ jobs:
|
||||
name: ${{ github.ref_name }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
body_path: release-notes.md
|
||||
files: |
|
||||
WandEnhancer/bin/Release/WandEnhancer.exe
|
||||
CHANGELOG.md
|
||||
fail_on_unmatched_files: true
|
||||
files: CHANGELOG.md
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,13 +6,14 @@ This repository patches the Wand Electron app from a .NET Framework WPF desktop
|
||||
|
||||
## Remote Web Panel
|
||||
|
||||
- The default local remote port is `3223`. Keep C# and frontend constants aligned.
|
||||
- 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-code` are redirected by `web-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 modular CommonJS source under `web-panel/bridge/source.cjs` and `web-panel/bridge/bridge-modules/`, but production runtime must be bundled/minified into `web-panel/dist/bridge.cjs` by `pnpm run build:bridge`. Do not copy `bridge-modules` into Wand or embed them as ASAR resources.
|
||||
- The Electron bridge is authored as TypeScript under `web-panel/bridge/src/`, but production runtime must be bundled/minified into `web-panel/dist/bridge.cjs` by `pnpm 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.DEV` dynamic 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 shadcn-style local primitives under `web-panel/src/components/ui/`.
|
||||
- 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 into `web-panel/dist/renderer-scripts/` by `pnpm run build:bridge`. Custom user scripts are selected in the WPF patch modal and copied from `PatchConfig.CustomScriptPaths`; only existing `.js` files are accepted. A local `renderer-scripts/` folder next to the patcher exe is still copied as an advanced fallback.
|
||||
- `web-panel/bridge/scripts/default/installed-apps-sync.js` resolves Wand's renderer services/store and publishes `My Games` snapshots through the `wand-remote-installed-apps` IPC channel. The synced list must mirror Wand's `my_games` source criteria: catalog games come from `installedGameVersions`, and extra installed unsupported titles come from `correlatedUnavailableTitles` whose `games[].correlationIds` match `installedApps`.
|
||||
- If the injected renderer cannot read a populated `correlatedUnavailableTitles` slice from the live store, `installed-apps-sync.js` must fall back to Wand's `/v3/unavailable_titles` correlation lookup through the renderer API client instead of degrading to raw install entries or an empty `My Games` list.
|
||||
@@ -22,7 +23,7 @@ This repository patches the Wand Electron app from a .NET Framework WPF desktop
|
||||
- The websocket `hello` snapshot must still send cached `installed_apps` and `game_status` even when no trainer snapshot is active yet; do not reintroduce a handshake path that returns early after `trainer_changed`.
|
||||
- Remote Play/Stop uses the websocket `remote_command` message. The bridge forwards it over `wand-remote-command` / `wand-remote-command-response`, and `installed-apps-sync.js` resolves Wand's trainer API + trainer service to launch a trainer for a `gameId` or end the current trainer.
|
||||
- Remote Play must construct Wand's real trainer launch request class (`69482.vO`) before calling `trainerService.launch(...)`. Passing a plain object launches the game process but breaks Wand's `getMetadata(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 inject `subscription:{period:"yearly",state:"active"}` into the response before it reaches the store: `getUserAccount` and `setAccountWandBrandExperience` (Resolver-style, service field via `<service_name>` placeholder) and `setAccountLanguage` (`BuildSetAccountLanguagePatch` PatchFactory — captures the real param names + the original `post("/v3/account/language",{...})` expr and wraps `.then`). Pro is `am(account) = !!account.subscription` (flags/512 are irrelevant). `setAccountLanguage` is 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 live `app-*.bundle.js` (do NOT trust `.source/new` — it is a different version).
|
||||
- Pro activation is a C# asar patch (`EPatchType.ActivatePro`, independent of the remote panel / bridge). It rewrites three account-returning service methods to inject `subscription:{period:"yearly",state:"active"}` into the response before it reaches the store: `getUserAccount` and `setAccountWandBrandExperience` (Resolver-style, service field via `<service_name>` placeholder) and `setAccountLanguage` (`BuildSetAccountLanguagePatch` PatchFactory — captures the real param names + the original `post("/v3/account/language",{...})` expr and wraps `.then`). A fourth patch (`setAccountReducer`) rewrites the `ACTION_SET_ACCOUNT` store reducer so any account write (periodic `refreshAccount`, push/profile updates, etc.) keeps Pro even when it bypasses those API methods. Pro is `am(account) = !!account.subscription` (flags/512 are irrelevant). `setAccountLanguage` is 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 live `app-*.bundle.js` (do NOT trust `.source/new` — it is a different version).
|
||||
|
||||
## ASAR Patch Pipeline
|
||||
|
||||
@@ -33,14 +34,14 @@ This repository patches the Wand Electron app from a .NET Framework WPF desktop
|
||||
- The `DevToolsOnF12` patch anchors on the Electron main-process `<app>.whenReady().then(` site and attaches a `before-input-event` hook to every `BrowserWindow.webContents`. Do not patch the renderer keydown listener — the minified `ACTION_OPEN_DEV_TOOLS` dispatch site is not stable across Wand releases.
|
||||
- Cheats can be pinned per game in the web panel via `pinned-storage.ts` (`localStorage` key `wand-remote.pinned-cheats.v1:<gameId>`). Pinned cheats render as a virtual `pinned` category at the top of the list; their normal category placement is preserved.
|
||||
- Custom quick presets are per trainer/game and stored by `preset-storage.ts` under `localStorage` key `wand-remote.presets.v1:<gameId-or-trainerId>`. Presets capture persistent cheat values only; do not include `button` one-shot cheats in saved presets.
|
||||
- All `localStorage` access in `web-panel/src/features/remote-panel/` MUST go through the shared helpers in `storage.ts` (`loadJson` / `saveJson` / `loadStringSet` / `saveStringSet`). Do not reintroduce per-module `try/catch` + `JSON.parse` duplication in `pinned-storage`, `preset-storage`, or `game-pin-storage`. Trainer/game storage IDs are derived through the shared `getTrainerStorageId(trainer)` helper in `storage.ts`; do not re-implement the `gameId → titleId → trainerId → 'global'` precedence inline.
|
||||
- All shared bridge port/path/IPC channel/WS-opcode/protocol-version constants live in `web-panel/bridge/bridge-modules/constants.cjs` (exports `IPC_CHANNEL`, `WS_OPCODE`, `BRIDGE_PROTOCOL_VERSION`, `BRIDGE_SERVER_VERSION`, `RENDERER_INJECTION_DELAYS_MS`). Do not redeclare `3223`, `/remote/*`, IPC channel strings, raw WS opcode numbers (1/8/9/10), or the 500/2000 ms injection delays inline. The renderer-script-side equivalents (e.g. `vO`/`TRAINER_LAUNCH_REQUEST_EXPORT_KEY`, snapshot key prefixes, bootstrap log throttle) live in `web-panel/bridge/scripts/default/installed-apps-sync/constants.js`.
|
||||
- UI string-union types follow the `E*` enum convention from `.claude/rules/frontend-conventions.md` (currently `ECheatType` in `protocol.ts`, `EConnectionStatus` in `state.ts`); the wire string values must remain on the right-hand side of the enum members. Reducer `PanelAction` `type` tags stay as discriminated-union string literals (the union itself provides the discrimination — converting it to an enum loses pattern matching).
|
||||
- Cheat input controls live one-per-file under `web-panel/src/features/remote-panel/controls/` (`ToggleControl`, `SliderControl`, `ScalarControl`, `NumberControl`, `ActionButton`, `SelectionControl`, `IncrementalControl`); shared `SliderTrack` / `StepButton` / `ControlInternalProps` are in `controls/shared.tsx` and number formatting helpers in `controls/format-number.ts`. `controls/CheatControl.tsx` is a thin dispatcher map keyed by `ECheatType` — do not inline new control bodies into it.
|
||||
- All `localStorage` access in `web-panel/src/` MUST go through `web-panel/src/shared/storage.ts` (`loadJson` / `saveJson` / `loadStringSet` / `saveStringSet`). Do not reintroduce per-capability `try/catch` + `JSON.parse` duplication. Trainer/game storage IDs are derived through the shared `getTrainerStorageId(trainer)` helper; do not re-implement the `gameId → 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 in `web-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` (currently `ECheatType` in `protocol/messages.ts`, `EConnectionStatus` in `remote-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/`; shared `SliderTrack` / `StepButton` / `ControlInternalProps` are in `controls/shared.tsx` and number formatting helpers in `controls/format-number.ts`. `controls/CheatControl.tsx` remains a thin dispatcher keyed by `ECheatType`.
|
||||
- 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-row `backdrop-blur-*` inside drawer lists.
|
||||
|
||||
## Validation
|
||||
|
||||
- Web panel build: `cd web-panel && pnpm run build` (runs type-check, Vite build, then `build:bridge` into `dist`).
|
||||
- Bridge/script syntax checks after build: `node --check web-panel/dist/bridge.cjs` and `node --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`, or `clsx`.
|
||||
- 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`, or `clsx`.
|
||||
|
||||
+28
-1
@@ -3,6 +3,33 @@
|
||||
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
|
||||
|
||||
- Official releases no longer include downloadable `.exe` files. To update, sync your fork and rerun the `Build executable` workflow, or follow the instructions in [How to use](https://github.com/k1tbyte/Wand-Enhancer#-how-to-use).
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed the built-in WandEnhancer updater. Official GitHub releases no longer ship executable assets.
|
||||
- Removed System.Net.Http
|
||||
- Removed self-signed certificate generation to prevent AV false positives.
|
||||
- Switched official releases to publish release notes only.
|
||||
|
||||
## [1.0.9.1] - 2026-06-24
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixed Pro features disappearing after a day or two when Wand refreshed account data in the background; account store updates now preserve the patched active subscription by @Kava-4 in #110. Related issue #106
|
||||
- Fixed the new Pro account reducer guard so normal account updates do not fail while keeping Pro active.
|
||||
|
||||
## [1.0.9.0] - 2026-06-15
|
||||
|
||||
### Features
|
||||
@@ -154,4 +181,4 @@ The newest entry must match the version in `WandEnhancer/Properties/AssemblyInfo
|
||||
|
||||
### Changes
|
||||
|
||||
- Basic ElectronJS wrapper over the original script.
|
||||
- Basic ElectronJS wrapper over the original script.
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ Suggestions for new features or improvements are welcome! Create an Issue descri
|
||||
git tag 1.0.8.0
|
||||
git push origin 1.0.8.0
|
||||
```
|
||||
6. GitHub Actions will validate the version, build the project, extract the matching changelog section, and publish the release automatically.
|
||||
6. GitHub Actions will validate the version, build the project, extract the matching changelog section, and publish a notes-only release automatically. Official releases do not attach compiled binaries.
|
||||
|
||||
## Code Style
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
# WandEnhancer
|
||||
|
||||
[](https://gitlab.com/kitbyte/wand-enhancer)
|
||||
[](https://www.virustotal.com/gui/file/f6897cf583e9f8ea11e0ee4c3fb99b86c50336b28de706e3e0b9181b4e3cf223)
|
||||
|
||||
</div>
|
||||
|
||||
<h4>An open-source interoperability tool designed to extend local client-side configurations and improve the UX of the Wand application.</h4>
|
||||
|
||||
**🚨 IMPORTANT NOTICE: THIS PROJECT HAS NO OFFICIAL YOUTUBE TUTORIALS OR GUIDES. 🚨
|
||||
There are no official videos showing how to install or use this tool. Scammers are creating fake tutorials using this project's name and placing malware/password stealers in the video descriptions. If you downloaded an .exe or archive from a YouTube link, YOU HAVE DOWNLOADED MALWARE. The only official, safe, and original source for this project is this exact GitHub repository. We are not responsible for third-party downloads.**
|
||||
**🚨 IMPORTANT NOTICE: THIS PROJECT HAS NO OFFICIAL YOUTUBE TUTORIALS, GUIDES, OR PREBUILT EXECUTABLE DOWNLOADS. 🚨
|
||||
There are no official videos showing how to install or use this tool. Scammers are creating fake tutorials using this project's name and placing malware/password stealers in the video descriptions. Official GitHub releases contain release notes only, not `.exe` files. If you downloaded an `.exe` or archive from a YouTube link, a random website, or a third-party mirror, you did not get it from this project. We are not responsible for third-party downloads.**
|
||||
|
||||
## 👾 Is it safe to use?
|
||||
|
||||
@@ -40,11 +39,20 @@ WandEnhancer includes a built-in **Remote Web Panel** allowing you to control ap
|
||||
|
||||
## 👀 How to use?
|
||||
|
||||
1. Go to the [Releases](https://github.com/k1tbyte/Wand-Enhancer/releases) page.
|
||||
2. Download the latest binary release.
|
||||
3. Run the enhancer to apply local client modifications.
|
||||
This repository does not publish official compiled binaries. Build your own executable from your own fork using GitHub Actions.
|
||||
|
||||
1. Sign in to GitHub and fork this repository.
|
||||
2. Open your fork, go to the **Actions** tab, and enable workflows if GitHub asks you to.
|
||||
3. Select the **Build executable** workflow.
|
||||
4. Click **Run workflow**, keep the default branch, and start the run.
|
||||
5. Wait for the workflow to finish, open the completed run, and download the artifact.
|
||||
6. Extract the artifact zip and run `WandEnhancer.exe` to apply local client modifications.
|
||||
|
||||
*Here how you do it:*
|
||||
|
||||
https://github.com/user-attachments/assets/7966cabe-0aa6-424d-8c2f-981ad91e0f91
|
||||
|
||||
|
||||
> Source archives are intended for developers who want to build the project locally. They are not prebuilt binaries.
|
||||
|
||||
## 🧩 Custom scripts
|
||||
|
||||
@@ -109,10 +117,16 @@ The build script installs the web panel dependencies, builds the frontend, compi
|
||||
|
||||
## ❓ Q&A
|
||||
|
||||
- **I applied the configuration but get stuck on 'Loading...'**
|
||||
- Just close the application completely and restart it.
|
||||
- **Why is there no `.exe` in GitHub Releases?**
|
||||
- Official releases are notes-only on purpose. The project no longer distributes prebuilt executables because unsigned or self-built patching tools are repeatedly reuploaded, mislabeled, and flagged by third-party scanners. Build the executable from your own fork using GitHub Actions instead.
|
||||
- **Where do I download the executable?**
|
||||
- From your own fork's **Actions** artifact after running the **Build executable** workflow. Do not download `.exe` files from YouTube descriptions, random mirrors, Discord attachments, or issue comments.
|
||||
- **Why does Windows Defender or SmartScreen warn about my build?**
|
||||
- The GitHub Actions artifact is unsigned and uncommon, so Windows may warn even when the code was built directly from your fork. Review the source, verify the workflow logs, and only run binaries you built yourself.
|
||||
- **Can I use a binary built by someone else?**
|
||||
- You can, but you should treat it as untrusted. This repository cannot verify or support third-party builds.
|
||||
- **Does this send data anywhere?**
|
||||
- No. All operations are strictly offline and local to your machine.
|
||||
- The desktop patching work is local to your machine. The Remote Web Panel is served from your PC on your local network.
|
||||
|
||||
---
|
||||
## 🖼️ Screenshots
|
||||
@@ -129,7 +143,14 @@ This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.md) fi
|
||||
|
||||
---
|
||||
## ❤️ Support
|
||||
[](https://ko-fi.com/kitbyte)
|
||||
|
||||
If you find this project useful, you can support its development using any of the options below 🙌
|
||||
|
||||
[](https://www.patreon.com/kitbyte/gift)
|
||||
[](https://tronscan.org/#/address/TQdvau8pAy5Tg1Aa588tTcPCFgbcHtuoxc)
|
||||
[](https://www.blockchain.com/explorer/addresses/btc/1EZKDcyU8REm9JW5xwXJqSpn5Xaq5yAWWX)
|
||||
[](https://etherscan.io/address/0xd904d9d0557f88bbb1c4ab3582b4ca0d8a730e8d)
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -138,4 +159,4 @@ This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.md) fi
|
||||
|
||||
---
|
||||
|
||||
[](https://www.star-history.com/#k1tbyte/Wand-Enhancer&Date)
|
||||
[](https://www.star-history.com/#k1tbyte/Wand-Enhancer&Date)
|
||||
|
||||
@@ -50,6 +50,17 @@ namespace WandEnhancer.Core
|
||||
return $"setAccountLanguage({parameters}){{return ({expr}).then(response=>{{response&&\"object\"==typeof response&&(response.subscription={{period:\"yearly\",state:\"active\"}});return response;}})}}";
|
||||
}
|
||||
|
||||
private static string BuildSetAccountReducerPatch(Match match)
|
||||
{
|
||||
var decl = RequireGroup(match, "decl", "setAccountReducer");
|
||||
var fn = RequireGroup(match, "fn", "setAccountReducer");
|
||||
var parameters = RequireGroup(match, "params", "setAccountReducer");
|
||||
var state = RequireGroup(match, "state", "setAccountReducer");
|
||||
var account = RequireGroup(match, "account", "setAccountReducer");
|
||||
return
|
||||
$"const {decl}=\"ACTION_SET_ACCOUNT\";function {fn}({parameters}){{const a={account}&&\"object\"==typeof {account}?{{...{account},subscription:{{period:\"yearly\",state:\"active\"}}}}:{account};return{{...{state},account:a}}}}";
|
||||
}
|
||||
|
||||
private static string BuildRemoteBridgeResetPatch(Match match)
|
||||
{
|
||||
var source = match.Value;
|
||||
@@ -143,6 +154,33 @@ namespace WandEnhancer.Core
|
||||
@"setAccountLanguage\((?<params>[^)]*)\)\{\s*return\s+(?<expr>this\.#\w+\.post\(""/v3/account/language"",\{[^}]*\}\))\s*;?\s*\}",
|
||||
RegexOptions.Singleline),
|
||||
PatchFactory = BuildSetAccountLanguagePatch
|
||||
},
|
||||
new PatchEntry
|
||||
{
|
||||
// Last-resort guard: any code path that dispatches ACTION_SET_ACCOUNT
|
||||
// (periodic refreshAccount, push updates, profile edits, etc.) must keep
|
||||
// subscription on the store object even when it bypasses the account API
|
||||
// service methods patched above.
|
||||
Name = "setAccountReducer",
|
||||
SearchHints = new[] { "ACTION_SET_ACCOUNT" },
|
||||
Target = new Regex(
|
||||
@"const (?<decl>\w+)=""ACTION_SET_ACCOUNT"";function (?<fn>\w+)\((?<params>[^)]*)\)\{return\{\.\.\.(?<state>\w+),account:(?<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\"))}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -213,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(@"(?<head>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\.(?<trainer>#[\w$]+)\)return;)(?=.*?e\.source!==(?<remoteSource>[\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(@"(?<head>#[\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\}\))(?<tail>\}\)\),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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Eine neue Version ist verfügbar</s:String>
|
||||
<s:String x:Key="mw_folder_path">Ordnerpfad</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Ordner nicht gefunden</s:String>
|
||||
<s:String x:Key="mw_patch">Anwenden</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Was werden wir verbessern?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Vor dem Update wird dringend empfohlen, Änderungen rückgängig zu machen, falls sie angewendet wurden</s:String>
|
||||
<s:String x:Key="up_current_version">Aktuelle Version</s:String>
|
||||
<s:String x:Key="up_latest_version">Neueste Version</s:String>
|
||||
<s:String x:Key="up_release_notes">Versionshinweise</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Für diese Version sind keine Versionshinweise verfügbar.</s:String>
|
||||
<s:String x:Key="up_show_more">Gesamtes Changelog anzeigen</s:String>
|
||||
<s:String x:Key="up_show_less">Nur aktuelle Hinweise anzeigen</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Changelog wird geladen...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Das vollständige Changelog konnte nicht geladen werden. Stattdessen werden die aktuellen Hinweise angezeigt.</s:String>
|
||||
<s:String x:Key="up_update_now">Jetzt aktualisieren</s:String>
|
||||
<s:String x:Key="up_popup_title">Update verfügbar!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">A new version is available</s:String>
|
||||
<s:String x:Key="mw_folder_path">Folder path</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Folder not found</s:String>
|
||||
<s:String x:Key="mw_patch">Enhance</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">What are we gonna enhance?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Before updating, it is strongly recommended to roll back modifications if they have been applied</s:String>
|
||||
<s:String x:Key="up_current_version">Current version</s:String>
|
||||
<s:String x:Key="up_latest_version">Latest version</s:String>
|
||||
<s:String x:Key="up_release_notes">Release notes</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Release notes are unavailable for this release.</s:String>
|
||||
<s:String x:Key="up_show_more">Show full changelog</s:String>
|
||||
<s:String x:Key="up_show_less">Show latest notes</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Loading changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Failed to load the full changelog. The latest notes are shown instead.</s:String>
|
||||
<s:String x:Key="up_update_now">Update now</s:String>
|
||||
<s:String x:Key="up_popup_title">Update available!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Una nueva versión está disponible</s:String>
|
||||
<s:String x:Key="mw_folder_path">Ruta de la carpeta</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Carpeta no encontrada</s:String>
|
||||
<s:String x:Key="mw_patch">Aplicar</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">¿Qué vamos a mejorar?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Antes de actualizar, se recomienda encarecidamente revertir las modificaciones si se han aplicado</s:String>
|
||||
<s:String x:Key="up_current_version">Versión actual</s:String>
|
||||
<s:String x:Key="up_latest_version">Última versión</s:String>
|
||||
<s:String x:Key="up_release_notes">Notas de la versión</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Las notas de la versión no están disponibles para esta versión.</s:String>
|
||||
<s:String x:Key="up_show_more">Mostrar changelog completo</s:String>
|
||||
<s:String x:Key="up_show_less">Mostrar solo las notas actuales</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Cargando changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">No se pudo cargar el changelog completo. Se muestran las notas actuales.</s:String>
|
||||
<s:String x:Key="up_update_now">Actualizar ahora</s:String>
|
||||
<s:String x:Key="up_popup_title">¡Actualización disponible!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Une nouvelle version est disponible</s:String>
|
||||
<s:String x:Key="mw_folder_path">Chemin du dossier</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Dossier non trouvé</s:String>
|
||||
<s:String x:Key="mw_patch">Appliquer</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Qu'allons-nous modifier ?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Avant la mise à jour, il est fortement recommandé d'annuler les modifications si elles ont été appliquées</s:String>
|
||||
<s:String x:Key="up_current_version">Version actuelle</s:String>
|
||||
<s:String x:Key="up_latest_version">Dernière version</s:String>
|
||||
<s:String x:Key="up_release_notes">Notes de version</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Les notes de version ne sont pas disponibles pour cette version.</s:String>
|
||||
<s:String x:Key="up_show_more">Afficher le changelog complet</s:String>
|
||||
<s:String x:Key="up_show_less">Afficher uniquement les notes actuelles</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Chargement du changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Impossible de charger le changelog complet. Les notes actuelles sont affichées à la place.</s:String>
|
||||
<s:String x:Key="up_update_now">Mettre à jour maintenant</s:String>
|
||||
<s:String x:Key="up_popup_title">Mise à jour disponible !</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">È disponibile una nuova versione</s:String>
|
||||
<s:String x:Key="mw_folder_path">Percorso cartella</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Cartella non trovata</s:String>
|
||||
<s:String x:Key="mw_patch">Applica</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Cosa modificheremo?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Prima dell'aggiornamento, si consiglia vivamente di annullare le modifiche se sono state applicate</s:String>
|
||||
<s:String x:Key="up_current_version">Versione corrente</s:String>
|
||||
<s:String x:Key="up_latest_version">Ultima versione</s:String>
|
||||
<s:String x:Key="up_release_notes">Note di rilascio</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Le note di rilascio non sono disponibili per questa versione.</s:String>
|
||||
<s:String x:Key="up_show_more">Mostra il changelog completo</s:String>
|
||||
<s:String x:Key="up_show_less">Mostra solo le note correnti</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Caricamento del changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Impossibile caricare il changelog completo. Vengono mostrate solo le note correnti.</s:String>
|
||||
<s:String x:Key="up_update_now">Aggiorna ora</s:String>
|
||||
<s:String x:Key="up_popup_title">Aggiornamento disponibile!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">新しいバージョンが利用可能です</s:String>
|
||||
<s:String x:Key="mw_folder_path">フォルダパス</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">フォルダが見つかりません</s:String>
|
||||
<s:String x:Key="mw_patch">適用</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">何を改善しますか?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">アップデート前に、変更が適用されている場合はロールバックすることを強くお勧めします</s:String>
|
||||
<s:String x:Key="up_current_version">現在のバージョン</s:String>
|
||||
<s:String x:Key="up_latest_version">最新バージョン</s:String>
|
||||
<s:String x:Key="up_release_notes">リリースノート</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">このリリースのリリースノートは利用できません。</s:String>
|
||||
<s:String x:Key="up_show_more">完全な変更履歴を表示</s:String>
|
||||
<s:String x:Key="up_show_less">最新のリリースノートのみ表示</s:String>
|
||||
<s:String x:Key="up_loading_changelog">変更履歴を読み込み中...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">完全な変更履歴を読み込めませんでした。代わりに最新のリリースノートを表示しています。</s:String>
|
||||
<s:String x:Key="up_update_now">今すぐ更新</s:String>
|
||||
<s:String x:Key="up_popup_title">アップデート利用可能!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Dostępna jest nowa wersja</s:String>
|
||||
<s:String x:Key="mw_folder_path">Ścieżka folderu</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Folder nie znaleziony</s:String>
|
||||
<s:String x:Key="mw_patch">Zastosuj</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Co będziemy ulepszać?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Przed aktualizacją zdecydowanie zaleca się cofnięcie zmian, jeśli zostały zastosowane</s:String>
|
||||
<s:String x:Key="up_current_version">Aktualna wersja</s:String>
|
||||
<s:String x:Key="up_latest_version">Najnowsza wersja</s:String>
|
||||
<s:String x:Key="up_release_notes">Informacje o wydaniu</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Informacje o wydaniu są niedostępne dla tej wersji.</s:String>
|
||||
<s:String x:Key="up_show_more">Pokaż cały changelog</s:String>
|
||||
<s:String x:Key="up_show_less">Pokaż tylko bieżące zmiany</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Ładowanie changeloga...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Nie udało się załadować pełnego changeloga. Zamiast tego wyświetlono bieżące zmiany.</s:String>
|
||||
<s:String x:Key="up_update_now">Aktualizuj teraz</s:String>
|
||||
<s:String x:Key="up_popup_title">Dostępna aktualizacja!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Uma nova versão está disponível</s:String>
|
||||
<s:String x:Key="mw_folder_path">Caminho da pasta</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Pasta não encontrada</s:String>
|
||||
<s:String x:Key="mw_patch">Aplicar</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">O que vamos melhorar?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Antes de atualizar, é altamente recomendável reverter as modificações se elas foram aplicadas</s:String>
|
||||
<s:String x:Key="up_current_version">Versão atual</s:String>
|
||||
<s:String x:Key="up_latest_version">Versão mais recente</s:String>
|
||||
<s:String x:Key="up_release_notes">Notas da versão</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">As notas da versão não estão disponíveis para esta versão.</s:String>
|
||||
<s:String x:Key="up_show_more">Mostrar changelog completo</s:String>
|
||||
<s:String x:Key="up_show_less">Mostrar apenas as notas atuais</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Carregando changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Falha ao carregar o changelog completo. As notas atuais estão sendo exibidas.</s:String>
|
||||
<s:String x:Key="up_update_now">Atualizar agora</s:String>
|
||||
<s:String x:Key="up_popup_title">Atualização disponível!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Доступна новая версия</s:String>
|
||||
<s:String x:Key="mw_folder_path">Путь к папке</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Папка не найдена</s:String>
|
||||
<s:String x:Key="mw_patch">Применить</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Что будем улучшать?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Перед обновлением настоятельно рекомендуется откатить изменения, если они были применены</s:String>
|
||||
<s:String x:Key="up_current_version">Текущая версия</s:String>
|
||||
<s:String x:Key="up_latest_version">Новая версия</s:String>
|
||||
<s:String x:Key="up_release_notes">Что нового</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Для этого релиза патчноуты недоступны.</s:String>
|
||||
<s:String x:Key="up_show_more">Показать весь changelog</s:String>
|
||||
<s:String x:Key="up_show_less">Показать только актуальные изменения</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Загрузка changelog...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Не удалось загрузить полный changelog. Показаны только актуальные изменения.</s:String>
|
||||
<s:String x:Key="up_update_now">Обновить сейчас</s:String>
|
||||
<s:String x:Key="up_popup_title">Доступно обновление!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Yeni bir sürüm mevcut</s:String>
|
||||
<s:String x:Key="mw_folder_path">Klasör yolu</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Klasör bulunamadı</s:String>
|
||||
<s:String x:Key="mw_patch">Uygula</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Neyi geliştireceğiz?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Güncellemeden önce, değişiklikler uygulandıysa geri almak şiddetle tavsiye edilir</s:String>
|
||||
<s:String x:Key="up_current_version">Geçerli sürüm</s:String>
|
||||
<s:String x:Key="up_latest_version">En son sürüm</s:String>
|
||||
<s:String x:Key="up_release_notes">Sürüm notları</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Bu sürüm için sürüm notları kullanılamıyor.</s:String>
|
||||
<s:String x:Key="up_show_more">Tüm changelog'u göster</s:String>
|
||||
<s:String x:Key="up_show_less">Yalnızca güncel notları göster</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Changelog yükleniyor...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Tam changelog yüklenemedi. Bunun yerine güncel notlar gösteriliyor.</s:String>
|
||||
<s:String x:Key="up_update_now">Şimdi güncelle</s:String>
|
||||
<s:String x:Key="up_popup_title">Güncelleme mevcut!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">Доступна нова версія</s:String>
|
||||
<s:String x:Key="mw_folder_path">Шлях до папки</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">Папку не знайдено</s:String>
|
||||
<s:String x:Key="mw_patch">Застосувати</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">Що будемо покращувати?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">Перед оновленням наполегливо рекомендується відкотити зміни, якщо вони були застосовані</s:String>
|
||||
<s:String x:Key="up_current_version">Поточна версія</s:String>
|
||||
<s:String x:Key="up_latest_version">Остання версія</s:String>
|
||||
<s:String x:Key="up_release_notes">Нотатки до релізу</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">Нотатки до цього релізу недоступні.</s:String>
|
||||
<s:String x:Key="up_show_more">Показати весь список змін</s:String>
|
||||
<s:String x:Key="up_show_less">Показати лише актуальні зміни</s:String>
|
||||
<s:String x:Key="up_loading_changelog">Завантаження списку змін...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">Не вдалося завантажити повний список змін. Натомість показано лише актуальні зміни.</s:String>
|
||||
<s:String x:Key="up_update_now">Оновити зараз</s:String>
|
||||
<s:String x:Key="up_popup_title">Доступне оновлення!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<!--#region MainWindow -->
|
||||
<s:String x:Key="mw_title">WandEnhancer</s:String>
|
||||
<s:String x:Key="mw_update_available">有新版本可用</s:String>
|
||||
<s:String x:Key="mw_folder_path">文件夹路径</s:String>
|
||||
<s:String x:Key="mw_folder_not_found">未找到文件夹</s:String>
|
||||
<s:String x:Key="mw_patch">增强</s:String>
|
||||
@@ -39,18 +38,4 @@
|
||||
<s:String x:Key="pv_popup_title">我们要增强什么?</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
<!--#region UpdatePopup -->
|
||||
<s:String x:Key="up_warning">在更新之前,强烈建议回滚已应用的修改</s:String>
|
||||
<s:String x:Key="up_current_version">当前版本</s:String>
|
||||
<s:String x:Key="up_latest_version">最新版本</s:String>
|
||||
<s:String x:Key="up_release_notes">更新说明</s:String>
|
||||
<s:String x:Key="up_release_notes_unavailable">此版本的更新说明不可用。</s:String>
|
||||
<s:String x:Key="up_show_more">显示完整更新日志</s:String>
|
||||
<s:String x:Key="up_show_less">仅显示当前说明</s:String>
|
||||
<s:String x:Key="up_loading_changelog">正在加载更新日志...</s:String>
|
||||
<s:String x:Key="up_changelog_failed">无法加载完整更新日志。当前仅显示本次说明。</s:String>
|
||||
<s:String x:Key="up_update_now">立即更新</s:String>
|
||||
<s:String x:Key="up_popup_title">有更新可用!</s:String>
|
||||
<!--#endregion -->
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -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.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.9.0")]
|
||||
[assembly: AssemblyVersion("1.0.9.3")]
|
||||
[assembly: AssemblyFileVersion("1.0.9.3")]
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WandEnhancer.Utils
|
||||
{
|
||||
public class UpdateReleaseInfo
|
||||
{
|
||||
public string Version { get; set; }
|
||||
|
||||
public string LatestNotes { get; set; }
|
||||
}
|
||||
|
||||
public class GitHubRelease
|
||||
{
|
||||
public class AssetsType
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("browser_download_url")]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
[JsonProperty("tag_name")]
|
||||
public string TagName { get; set; }
|
||||
|
||||
[JsonProperty("assets")]
|
||||
public AssetsType[] Assets { get; set; }
|
||||
|
||||
[JsonProperty("body")]
|
||||
public string Body { get; set; }
|
||||
|
||||
[JsonProperty("published_at")]
|
||||
public DateTimeOffset PublishedAt { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Updater
|
||||
{
|
||||
private GitHubRelease _release = null;
|
||||
private UpdateReleaseInfo _updateInfo = null;
|
||||
private string _fullChangelog = null;
|
||||
private static readonly HttpClient _httpClient = new HttpClient()
|
||||
{
|
||||
DefaultRequestHeaders =
|
||||
{
|
||||
{ "User-Agent", "GitHub-Updater" }
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly string ApiUrl = $"https://api.github.com/repos/{Constants.Owner}/{Constants.RepoName}/releases/latest";
|
||||
private static readonly string ReleasesApiUrl = $"https://api.github.com/repos/{Constants.Owner}/{Constants.RepoName}/releases?per_page=20";
|
||||
public async Task<bool> CheckForUpdates()
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var response = await _httpClient.GetAsync(ApiUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
_release = JsonConvert.DeserializeObject<GitHubRelease>(await response.Content.ReadAsStringAsync());
|
||||
_updateInfo = null;
|
||||
_fullChangelog = null;
|
||||
|
||||
if (_release == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var latestVersion = ParseVersion(_release.TagName);
|
||||
|
||||
if (latestVersion <= currentVersion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_updateInfo = new UpdateReleaseInfo
|
||||
{
|
||||
Version = NormalizeVersion(_release.TagName),
|
||||
LatestNotes = NormalizeText(_release.Body)
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UpdateReleaseInfo> GetUpdateInfoAsync()
|
||||
{
|
||||
if (_updateInfo != null)
|
||||
{
|
||||
return _updateInfo;
|
||||
}
|
||||
|
||||
return await CheckForUpdates()
|
||||
? _updateInfo
|
||||
: null;
|
||||
}
|
||||
|
||||
public async Task<string> GetFullChangelogAsync()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_fullChangelog))
|
||||
{
|
||||
return NormalizeText(_fullChangelog);
|
||||
}
|
||||
|
||||
_fullChangelog = await TryLoadFullChangelogAsync();
|
||||
|
||||
return NormalizeText(_fullChangelog);
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
if (_release == null)
|
||||
{
|
||||
throw new Exception("No release found");
|
||||
}
|
||||
|
||||
var asset = _release.Assets.FirstOrDefault(o => o.Name.EndsWith(".exe"));
|
||||
if(asset == null)
|
||||
{
|
||||
throw new Exception("No asset found");
|
||||
}
|
||||
|
||||
// download to temp
|
||||
var downloadPath = Path.Combine(Path.GetTempPath(), asset.Name);
|
||||
|
||||
using(var response = await _httpClient.GetAsync(asset.Url))
|
||||
using(var fileStream = File.Create(downloadPath))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
ApplyUpdate(downloadPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void ApplyUpdate(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentExecutable = Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
var psCommand = $"Start-Sleep -Seconds 2; " +
|
||||
$"Copy-Item -Path '{filePath}' -Destination '{currentExecutable}' -Force; " +
|
||||
$"Remove-Item -Path '{filePath}' -Force; " +
|
||||
$"Start-Sleep -Seconds 1; " +
|
||||
$"Start-Process -FilePath '{currentExecutable}';";
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $"-WindowStyle Hidden -ExecutionPolicy Bypass -Command \"{psCommand}\"",
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
Task.Delay(500).ContinueWith(_ =>
|
||||
{
|
||||
App.Shutdown();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Update failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Version ParseVersion(string versionTag)
|
||||
{
|
||||
return new Version(NormalizeVersion(versionTag));
|
||||
}
|
||||
|
||||
private static string NormalizeVersion(string versionTag)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(versionTag))
|
||||
{
|
||||
throw new ArgumentException("Version tag cannot be empty.", nameof(versionTag));
|
||||
}
|
||||
|
||||
return versionTag.Trim().TrimStart('v', 'V');
|
||||
}
|
||||
|
||||
private static string NormalizeText(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return NormalizeLineEndings(text).Trim();
|
||||
}
|
||||
|
||||
private static string NormalizeLineEndings(string text)
|
||||
{
|
||||
return text
|
||||
.Replace("\r\n", "\n")
|
||||
.Replace('\r', '\n');
|
||||
}
|
||||
|
||||
private static async Task<string> TryLoadFullChangelogAsync()
|
||||
{
|
||||
return await TryBuildReleaseHistoryAsync();
|
||||
}
|
||||
|
||||
private static async Task<string> TryBuildReleaseHistoryAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(ReleasesApiUrl);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var releases = JsonConvert.DeserializeObject<GitHubRelease[]>(await response.Content.ReadAsStringAsync());
|
||||
if (releases == null || releases.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return BuildReleaseHistory(releases);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildReleaseHistory(GitHubRelease[] releases)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var release in releases.Where(item => !string.IsNullOrWhiteSpace(item?.TagName)))
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.Append("## [")
|
||||
.Append(NormalizeVersion(release.TagName))
|
||||
.Append("]");
|
||||
|
||||
if (release.PublishedAt != default(DateTimeOffset))
|
||||
{
|
||||
builder.Append(" - ")
|
||||
.Append(release.PublishedAt.UtcDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
var notes = NormalizeText(release.Body);
|
||||
if (string.IsNullOrWhiteSpace(notes))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine();
|
||||
builder.Append(notes);
|
||||
}
|
||||
|
||||
return NormalizeText(builder.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,6 @@
|
||||
v 1.0.0
|
||||
</TextBlock>
|
||||
|
||||
<Button Background="SpringGreen" Foreground="{DynamicResource Muted}"
|
||||
FontWeight="Medium" Padding="20 0" Margin="10 5 20 5"
|
||||
ToolTip="Click to update"
|
||||
Command="{Binding UpdateCommand}"
|
||||
Visibility="{Binding IsUpdateAvailable, Converter={StaticResource ToVisibilityConverter}}"
|
||||
Content="{DynamicResource mw_update_available}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
@@ -227,4 +221,4 @@
|
||||
<controls:PopupHost x:Name="PopupHost"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
@@ -18,8 +17,6 @@ namespace WandEnhancer.View.MainWindow
|
||||
{
|
||||
private readonly MainWindow _view;
|
||||
public ObservableCollection<LogEntry> LogList { get; set; } = new ObservableCollection<LogEntry>();
|
||||
private static Updater _updater = new Updater();
|
||||
|
||||
private WeModConfig _weModConfig;
|
||||
|
||||
public WeModConfig WeModInfo
|
||||
@@ -61,18 +58,9 @@ namespace WandEnhancer.View.MainWindow
|
||||
set => SetProperty(ref _alreadyPatched, value);
|
||||
}
|
||||
|
||||
private bool _isUpdateAvailable;
|
||||
|
||||
public bool IsUpdateAvailable
|
||||
{
|
||||
get => _isUpdateAvailable;
|
||||
set => SetProperty(ref _isUpdateAvailable, value);
|
||||
}
|
||||
|
||||
public RelayCommand SetFolderPathCommand { get; }
|
||||
public RelayCommand ApplyPatchCommand { get; }
|
||||
public RelayCommand RestoreBackupCommand { get; }
|
||||
public RelayCommand UpdateCommand { get; }
|
||||
public RelayCommand OpenSettingsCommand { get; }
|
||||
public RelayCommand CopyLogsCommand { get; }
|
||||
public RelayCommand ExportLogsCommand { get; }
|
||||
@@ -185,36 +173,6 @@ namespace WandEnhancer.View.MainWindow
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnUpdate(object param)
|
||||
{
|
||||
var updateInfo = await _updater.GetUpdateInfoAsync();
|
||||
if (updateInfo == null)
|
||||
{
|
||||
Log("No update details are available right now.", ELogType.Warn);
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindow.Instance.OpenPopup(new UpdatePopup(Constants.Version.ToString(), updateInfo.Version,
|
||||
updateInfo.LatestNotes, () =>
|
||||
{
|
||||
MainWindow.Instance.ClosePopup();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _updater.Update();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"Failed to update: {e.Message}", ELogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Log("WandEnhancer updated successfully. Restarting...", ELogType.Success);
|
||||
});
|
||||
}, () => _updater.GetFullChangelogAsync()), Application.Current.FindResource("up_popup_title") as string);
|
||||
}
|
||||
|
||||
private void OnOpenSettings(object param)
|
||||
{
|
||||
MainWindow.Instance.OpenPopup(new SettingsPopup(), Application.Current.FindResource("settings_title") as string);
|
||||
@@ -280,16 +238,10 @@ namespace WandEnhancer.View.MainWindow
|
||||
|
||||
public MainWindowVm(MainWindow view)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var isUpdateAvailable = await _updater.CheckForUpdates();
|
||||
Application.Current.Dispatcher.Invoke(() => IsUpdateAvailable = isUpdateAvailable);
|
||||
});
|
||||
_view = view;
|
||||
SetFolderPathCommand = new RelayCommand(OnFolderPathSelection);
|
||||
ApplyPatchCommand = new RelayCommand(OnPatching);
|
||||
RestoreBackupCommand = new RelayCommand(OnBackupRestoring);
|
||||
UpdateCommand = new RelayCommand(OnUpdate);
|
||||
OpenSettingsCommand = new RelayCommand(OnOpenSettings);
|
||||
CopyLogsCommand = new RelayCommand(OnCopyLogs);
|
||||
ExportLogsCommand = new RelayCommand(OnExportLogs);
|
||||
@@ -301,4 +253,4 @@ namespace WandEnhancer.View.MainWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<UserControl x:Class="WandEnhancer.View.Popups.UpdatePopup"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="Auto" d:DesignWidth="Auto"
|
||||
Background="{DynamicResource Background}"
|
||||
Foreground="{DynamicResource MutedForeground}"
|
||||
FontWeight="Medium"
|
||||
FontSize="13">
|
||||
<Grid MinWidth="500" MaxWidth="620">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="UpdateActionButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{DynamicResource Primary}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Primary}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource Primary}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Primary}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" HorizontalAlignment="Left" MaxWidth="560"
|
||||
Padding="9 4" CornerRadius="4"
|
||||
Background="{DynamicResource Muted}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Ellipse Width="7" Height="7" Fill="{DynamicResource Destructive}"
|
||||
Margin="0 0 8 0" VerticalAlignment="Center" />
|
||||
<TextBlock Foreground="{DynamicResource Foreground}" FontSize="11.5"
|
||||
Text="{DynamicResource up_warning}" TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<WrapPanel Grid.Row="1" Margin="0 10 0 0" Orientation="Horizontal">
|
||||
<Border Padding="8 4" Margin="0 0 8 0" CornerRadius="4"
|
||||
Background="{DynamicResource Muted}" BorderBrush="{DynamicResource Border}" BorderThickness="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0 0 7 0" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource MutedForeground}" FontSize="10.5"
|
||||
Text="{DynamicResource up_current_version}" />
|
||||
<TextBlock x:Name="CurrentVersionValue" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource Foreground}" FontSize="12.5" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="8 4" CornerRadius="4"
|
||||
Background="{DynamicResource Primary}" BorderThickness="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0 0 7 0" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryForeground}" FontSize="10.5"
|
||||
Text="{DynamicResource up_latest_version}" />
|
||||
<TextBlock x:Name="LatestVersionValue" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryForeground}" FontSize="12.5" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<TextBlock Grid.Row="2" Margin="0 12 0 6" Foreground="{DynamicResource Foreground}"
|
||||
FontSize="14" FontWeight="Bold" Text="{DynamicResource up_release_notes}" />
|
||||
|
||||
<Border Grid.Row="3"
|
||||
Background="{DynamicResource Muted}" BorderBrush="{DynamicResource Border}" BorderThickness="1" CornerRadius="4">
|
||||
<ScrollViewer x:Name="NotesScrollViewer"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
CanContentScroll="False">
|
||||
<TextBox x:Name="NotesTextBlock" Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Padding="12"
|
||||
BorderThickness="0" IsReadOnly="True"
|
||||
Foreground="{DynamicResource Foreground}"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="4" Margin="0 12 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button x:Name="ShowMoreButton" Grid.Column="1" Margin="0 0 10 0"
|
||||
Padding="12 5" Content="{DynamicResource up_show_more}"
|
||||
Click="OnShowMoreClick" />
|
||||
|
||||
<Button Grid.Column="2" Padding="18 5" Style="{StaticResource UpdateActionButton}"
|
||||
Content="{DynamicResource up_update_now}"
|
||||
Click="OnUpdateClick" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WandEnhancer.View.Popups
|
||||
{
|
||||
public partial class UpdatePopup : UserControl
|
||||
{
|
||||
private readonly Action _onUpdate;
|
||||
private readonly Func<Task<string>> _loadFullChangelog;
|
||||
private readonly string _latestNotes;
|
||||
private string _fullChangelog;
|
||||
private bool _showingFullChangelog;
|
||||
|
||||
public UpdatePopup(string currentVersion, string latestVersion, string latestNotes, Action onUpdate,
|
||||
Func<Task<string>> loadFullChangelog)
|
||||
{
|
||||
_onUpdate = onUpdate;
|
||||
_loadFullChangelog = loadFullChangelog;
|
||||
InitializeComponent();
|
||||
|
||||
CurrentVersionValue.Text = currentVersion;
|
||||
LatestVersionValue.Text = latestVersion;
|
||||
_latestNotes = string.IsNullOrWhiteSpace(latestNotes)
|
||||
? GetResourceText("up_release_notes_unavailable")
|
||||
: latestNotes;
|
||||
|
||||
SetNotesText(_latestNotes);
|
||||
ShowMoreButton.Visibility = loadFullChangelog == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private void OnUpdateClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_onUpdate();
|
||||
}
|
||||
|
||||
private async void OnShowMoreClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_loadFullChangelog == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_showingFullChangelog)
|
||||
{
|
||||
SetNotesText(_latestNotes);
|
||||
ShowMoreButton.Content = GetResourceText("up_show_more");
|
||||
_showingFullChangelog = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_fullChangelog))
|
||||
{
|
||||
ShowMoreButton.IsEnabled = false;
|
||||
ShowMoreButton.Content = GetResourceText("up_loading_changelog");
|
||||
|
||||
try
|
||||
{
|
||||
_fullChangelog = await _loadFullChangelog();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ShowMoreButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_fullChangelog))
|
||||
{
|
||||
ShowMoreButton.Content = GetResourceText("up_show_more");
|
||||
SetNotesText(string.Concat(
|
||||
_latestNotes,
|
||||
Environment.NewLine,
|
||||
Environment.NewLine,
|
||||
GetResourceText("up_changelog_failed")));
|
||||
return;
|
||||
}
|
||||
|
||||
SetNotesText(_fullChangelog);
|
||||
ShowMoreButton.Content = GetResourceText("up_show_less");
|
||||
_showingFullChangelog = true;
|
||||
}
|
||||
|
||||
private void SetNotesText(string text)
|
||||
{
|
||||
NotesTextBlock.Text = text ?? string.Empty;
|
||||
NotesScrollViewer.ScrollToTop();
|
||||
}
|
||||
|
||||
private static string GetResourceText(string key)
|
||||
{
|
||||
return Application.Current.TryFindResource(key) as string ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
@@ -84,7 +83,6 @@
|
||||
<Compile Include="ReactiveUICore\RelayCommand.cs" />
|
||||
<Compile Include="Utils\Common.cs" />
|
||||
<Compile Include="Utils\Extensions.cs" />
|
||||
<Compile Include="Utils\Updater.cs" />
|
||||
<Compile Include="Utils\Win32\Shortcut.cs" />
|
||||
<Compile Include="View\Controls\InfoItem.xaml.cs">
|
||||
<DependentUpon>InfoItem.xaml</DependentUpon>
|
||||
@@ -103,9 +101,6 @@
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="View\Popups\UpdatePopup.xaml.cs">
|
||||
<DependentUpon>UpdatePopup.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Page Include="Locale\lang.en-US.xaml" />
|
||||
<Page Include="Locale\lang.zh-CN.xaml" />
|
||||
<Page Include="Locale\lang.de-DE.xaml" />
|
||||
@@ -126,7 +121,6 @@
|
||||
<Page Include="View\MainWindow\MainWindow.xaml" />
|
||||
<Page Include="View\Popups\PatchVectorsPopup.xaml" />
|
||||
<Page Include="View\Popups\SettingsPopup.xaml" />
|
||||
<Page Include="View\Popups\UpdatePopup.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
@@ -203,4 +197,4 @@
|
||||
<Exec Command=""$(ILRepackExe)" /allowMultiple /copyattrs /out:"$(OutputPath)$(AssemblyName).exe" "$(MainAssembly)" $(DllList)" />
|
||||
<Delete Files="@(AssemblyList)" ContinueOnError="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
@@ -111,45 +111,5 @@ Invoke-Step 'Build solution' {
|
||||
& $msbuild $solutionPath /m /p:Configuration=$Configuration '/p:Platform=Any CPU' /t:Build
|
||||
}
|
||||
|
||||
# Code-sign the release executable. A self-signed signature notably lowers
|
||||
# false-positive AV/VirusTotal detections. The cert is reused across builds and
|
||||
# generated on first use, so no secrets or env configuration are required.
|
||||
if ($Configuration -eq 'Release') {
|
||||
Write-Host '==> Sign WandEnhancer.exe' -ForegroundColor Cyan
|
||||
|
||||
$exePath = Join-Path $repoRoot "WandEnhancer/bin/$Configuration/WandEnhancer.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
throw "Executable not found for signing: $exePath"
|
||||
}
|
||||
|
||||
$signingSubject = 'CN=Wand-Enhancer'
|
||||
$cert = Get-ChildItem Cert:\CurrentUser\My |
|
||||
Where-Object { $_.Subject -eq $signingSubject -and $_.HasPrivateKey } |
|
||||
Select-Object -First 1
|
||||
if (-not $cert) {
|
||||
$cert = New-SelfSignedCertificate `
|
||||
-Subject $signingSubject `
|
||||
-Type CodeSigningCert `
|
||||
-CertStoreLocation Cert:\CurrentUser\My `
|
||||
-KeyExportPolicy Exportable `
|
||||
-KeyUsage DigitalSignature `
|
||||
-KeyAlgorithm RSA `
|
||||
-KeyLength 2048 `
|
||||
-HashAlgorithm SHA256 `
|
||||
-NotAfter (Get-Date).AddYears(5)
|
||||
Write-Host "Generated self-signed code-signing certificate: $($cert.Subject) [$($cert.Thumbprint)]"
|
||||
}
|
||||
|
||||
$signature = Set-AuthenticodeSignature -FilePath $exePath -Certificate $cert -HashAlgorithm SHA256
|
||||
# A self-signed root is intentionally untrusted, so the status is
|
||||
# 'UnknownError' (untrusted root) even though the signature is embedded.
|
||||
# Only a missing SignerCertificate means signing actually failed.
|
||||
if (-not $signature.SignerCertificate) {
|
||||
throw "Signing failed: $($signature.Status) - $($signature.StatusMessage)"
|
||||
}
|
||||
|
||||
Write-Host "Signed $exePath [$($cert.Thumbprint)] (status: $($signature.Status))"
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "Build completed successfully ($Configuration)." -ForegroundColor Green
|
||||
Write-Host "Build completed successfully ($Configuration)." -ForegroundColor Green
|
||||
|
||||
Reference in New Issue
Block a user