Release 1.0.7.0: Remote Web Panel & Stability Fixes

This commit is contained in:
kitbyte
2026-05-01 22:23:27 +03:00
parent 5273930eef
commit fa753b4a34
76 changed files with 6676 additions and 50 deletions
+259 -13
View File
@@ -4,27 +4,45 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using AsarSharp;
using Newtonsoft.Json;
using WandEnhancer.Models;
using WandEnhancer.Utils;
using WandEnhancer.View.MainWindow;
using Application = System.Windows.Application;
namespace WandEnhancer.Core
{
public class Enhancer
{
private const string ResourcesDirectoryName = "resources";
private const string AppAsarFileName = "app.asar";
private const string AppAsarUnpackedDirectoryName = "app.asar.unpacked";
private const string AppAsarBackupFileName = "app.asar.backup";
private const string AppAsarUnpackedBackupDirectoryName = "app.asar.unpacked.backup";
private const string WebPanelDirectoryName = "web-panel";
private const string WebPanelDistDirectoryName = "dist";
private const string WebPanelBridgeDirectoryName = "bridge";
private const string WebPanelScriptsDirectoryName = "scripts";
private const string DefaultScriptsDirectoryName = "default";
private const string LocalCustomScriptsDirectoryName = "renderer-scripts";
private const string RemotePanelDirectoryName = "remote-panel";
private const string RemoteBridgeSourceFileName = "wand-remote-bridge.cjs";
private const string RemoteBridgeTargetFileName = "bridge.cjs";
private const string RemoteRendererScriptsDirectoryName = "renderer-scripts";
private const string EmbeddedRemotePanelDistPrefix = "remote-panel/dist/";
private const string EmbeddedRemotePanelBridgeResourceName = "remote-panel/bridge.cjs";
private const string EmbeddedRemotePanelDefaultScriptsPrefix = "remote-panel/renderer-scripts/";
private const string JavaScriptFileExtension = ".js";
private const string JavaScriptFileSearchPattern = "*.js";
private const string DuplicateScriptSuffix = ".custom";
private const int FirstDuplicateScriptIndex = 1;
private readonly WeModConfig _weModConfig;
private readonly Action<string, ELogType> _logger;
private readonly PatchConfig _config;
private readonly string _asarPath;
private readonly string _backupPath;
private readonly string _unpackedPath;
private readonly string _unpackedBackupPath;
public Enhancer(WeModConfig weModConfig, Action<string, ELogType> logger, PatchConfig config)
{
@@ -32,9 +50,10 @@ namespace WandEnhancer.Core
_logger = logger;
_config = config;
_asarPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar");
_unpackedPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.unpacked");
_backupPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.backup");
_asarPath = Path.Combine(weModConfig.RootDirectory, ResourcesDirectoryName, AppAsarFileName);
_unpackedPath = Path.Combine(weModConfig.RootDirectory, ResourcesDirectoryName, AppAsarUnpackedDirectoryName);
_backupPath = Path.Combine(weModConfig.RootDirectory, ResourcesDirectoryName, AppAsarBackupFileName);
_unpackedBackupPath = Path.Combine(weModConfig.RootDirectory, ResourcesDirectoryName, AppAsarUnpackedBackupDirectoryName);
}
private string ApplyJsPatch(string fileName, string js, EnhancerConfig.PatchEntry patch, EPatchType patchType)
@@ -90,7 +109,6 @@ namespace WandEnhancer.Core
throw new Exception("[ENHANCER] No app bundle found");
}
// Track patches that still need to be completed
var remainingPatches = new HashSet<EPatchType>(_config.PatchTypes);
var enhancerConfig = EnhancerConfig.GetInstance();
@@ -103,17 +121,14 @@ namespace WandEnhancer.Core
string data = File.ReadAllText(item);
// Iterate over a copy of the list so we can modify the HashSet
foreach (var entry in remainingPatches.ToList())
{
var entries = enhancerConfig[entry];
foreach (var patchEntry in entries)
{
// Update data in memory so subsequent patches in the same file work on latest content
data = ApplyJsPatch(item, data, patchEntry, entry);
}
// Check if all entries for this patch type are applied
if (entries.All(x => x.Applied))
{
remainingPatches.Remove(entry);
@@ -128,6 +143,215 @@ namespace WandEnhancer.Core
}
}
private static string FindWorkspacePath(params string[] segments)
{
string current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
while (!string.IsNullOrEmpty(current))
{
string candidate = Path.Combine(new[] { current }.Concat(segments).ToArray());
if (Directory.Exists(candidate) || File.Exists(candidate))
{
return candidate;
}
current = Directory.GetParent(current)?.FullName;
}
throw new FileNotFoundException($"Required workspace artifact not found: {Path.Combine(segments)}");
}
private static void CopyDirectory(string sourceDir, string destinationDir)
{
Directory.CreateDirectory(destinationDir);
foreach (var directory in Directory.GetDirectories(sourceDir, "*", SearchOption.AllDirectories))
{
var relativePath = directory.Substring(sourceDir.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
Directory.CreateDirectory(Path.Combine(destinationDir, relativePath));
}
foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories))
{
var relativePath = file.Substring(sourceDir.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var destinationPath = Path.Combine(destinationDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath) ?? destinationDir);
File.Copy(file, destinationPath, true);
}
}
private static int CopyJavaScriptFiles(string sourceDir, string destinationDir)
{
if (string.IsNullOrEmpty(sourceDir) || !Directory.Exists(sourceDir))
{
return 0;
}
Directory.CreateDirectory(destinationDir);
int copied = 0;
foreach (var file in Directory.GetFiles(sourceDir, JavaScriptFileSearchPattern, SearchOption.TopDirectoryOnly))
{
File.Copy(file, GetAvailableScriptPath(destinationDir, Path.GetFileName(file)));
copied++;
}
return copied;
}
private static string GetAvailableScriptPath(string destinationDir, string fileName)
{
string destinationPath = Path.Combine(destinationDir, fileName);
if (!File.Exists(destinationPath))
{
return destinationPath;
}
string name = Path.GetFileNameWithoutExtension(fileName);
string extension = Path.GetExtension(fileName);
for (int index = FirstDuplicateScriptIndex; ; index++)
{
destinationPath = Path.Combine(destinationDir, $"{name}{DuplicateScriptSuffix}{index}{extension}");
if (!File.Exists(destinationPath))
{
return destinationPath;
}
}
}
private static int CopyEmbeddedDirectory(string resourcePrefix, string destinationDir)
{
var assembly = Assembly.GetExecutingAssembly();
var resourceNames = assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(resourcePrefix, StringComparison.Ordinal))
.ToList();
if (resourceNames.Count == 0)
{
return 0;
}
Directory.CreateDirectory(destinationDir);
foreach (var resourceName in resourceNames)
{
var relativePath = resourceName.Substring(resourcePrefix.Length)
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
var destinationPath = Path.Combine(destinationDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath) ?? destinationDir);
using (var resource = assembly.GetManifestResourceStream(resourceName))
{
if (resource == null)
{
throw new FileNotFoundException($"Embedded resource not found: {resourceName}");
}
using (var output = File.Create(destinationPath))
{
resource.CopyTo(output);
}
}
}
return resourceNames.Count;
}
private static bool CopyEmbeddedFile(string resourceName, string destinationPath)
{
var assembly = Assembly.GetExecutingAssembly();
using (var resource = assembly.GetManifestResourceStream(resourceName))
{
if (resource == null)
{
return false;
}
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath) ?? ".");
using (var output = File.Create(destinationPath))
{
resource.CopyTo(output);
}
}
return true;
}
private static string FindLocalCustomScriptsPath()
{
string executableDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (string.IsNullOrEmpty(executableDirectory))
{
return null;
}
string localScripts = Path.Combine(executableDirectory, LocalCustomScriptsDirectoryName);
return Directory.Exists(localScripts) ? localScripts : null;
}
private static int CopySelectedJavaScriptFiles(IEnumerable<string> files, string destinationDir)
{
if (files == null)
{
return 0;
}
Directory.CreateDirectory(destinationDir);
int copied = 0;
foreach (var file in files.Where(IsJavaScriptFile).Distinct(StringComparer.OrdinalIgnoreCase))
{
File.Copy(file, GetAvailableScriptPath(destinationDir, Path.GetFileName(file)));
copied++;
}
return copied;
}
private static bool IsJavaScriptFile(string file)
{
return File.Exists(file) && string.Equals(Path.GetExtension(file), JavaScriptFileExtension, StringComparison.OrdinalIgnoreCase);
}
private void InjectRemotePanelFiles()
{
if (!_config.PatchTypes.Contains(EPatchType.RemoteWebPanelPreview))
{
return;
}
string localCustomScriptsRoot = FindLocalCustomScriptsPath();
string targetRoot = Path.Combine(_unpackedPath, RemotePanelDirectoryName);
string targetScriptsRoot = Path.Combine(targetRoot, RemoteRendererScriptsDirectoryName);
string targetBridgePath = Path.Combine(targetRoot, RemoteBridgeTargetFileName);
if (Directory.Exists(targetRoot))
{
Directory.Delete(targetRoot, true);
}
if (CopyEmbeddedDirectory(EmbeddedRemotePanelDistPrefix, targetRoot) == 0)
{
CopyDirectory(FindWorkspacePath(WebPanelDirectoryName, WebPanelDistDirectoryName), targetRoot);
}
if (!CopyEmbeddedFile(EmbeddedRemotePanelBridgeResourceName, targetBridgePath))
{
File.Copy(FindWorkspacePath(WebPanelDirectoryName, WebPanelBridgeDirectoryName, RemoteBridgeSourceFileName), targetBridgePath, true);
}
int defaultScriptCount = CopyEmbeddedDirectory(EmbeddedRemotePanelDefaultScriptsPrefix, targetScriptsRoot);
if (defaultScriptCount == 0)
{
defaultScriptCount = CopyJavaScriptFiles(FindWorkspacePath(WebPanelDirectoryName, WebPanelScriptsDirectoryName, DefaultScriptsDirectoryName), targetScriptsRoot);
}
int selectedScriptCount = CopySelectedJavaScriptFiles(_config.CustomScriptPaths, targetScriptsRoot);
int localScriptCount = CopyJavaScriptFiles(localCustomScriptsRoot, targetScriptsRoot);
_logger($"[ENHANCER] Injected remote panel assets and renderer scripts into app.asar (default: {defaultScriptCount}, selected: {selectedScriptCount}, local: {localScriptCount})", ELogType.Info);
}
private void AttachProxyDll()
{
var assembly = Assembly.GetExecutingAssembly();
@@ -154,7 +378,28 @@ namespace WandEnhancer.Core
}
else
{
_logger("[ENHANCER] Backup already exists", ELogType.Warn);
_logger("[ENHANCER] Backup found, restoring pristine app.asar before patching...", ELogType.Info);
File.Copy(_backupPath, _asarPath, true);
}
if (!Directory.Exists(_unpackedBackupPath) && Directory.Exists(_unpackedPath))
{
_logger("[ENHANCER] Creating backup of app.asar.unpacked...", ELogType.Info);
CopyDirectory(_unpackedPath, _unpackedBackupPath);
}
else if (Directory.Exists(_unpackedBackupPath))
{
_logger("[ENHANCER] Restoring pristine app.asar.unpacked before patching...", ELogType.Info);
if (Directory.Exists(_unpackedPath))
{
Directory.Delete(_unpackedPath, true);
}
CopyDirectory(_unpackedBackupPath, _unpackedPath);
}
else if (!Directory.Exists(_unpackedPath))
{
throw new Exception("[ENHANCER] app.asar.unpacked is missing and no backup exists. Restore the original Wand installation files or reinstall Wand, then patch again.");
}
if(!File.Exists(_asarPath))
@@ -173,6 +418,7 @@ namespace WandEnhancer.Core
}
PatchAsar();
InjectRemotePanelFiles();
try
{
+65 -5
View File
@@ -7,6 +7,9 @@ namespace WandEnhancer.Core
{
public static class EnhancerConfig
{
private const int RemoteWebPanelDefaultPort = 3223;
private static readonly string RemoteWebPanelFallbackUrl = $"http://localhost:{RemoteWebPanelDefaultPort}/remote/";
public class ResolveContext
{
public string Placeholder { get; set; }
@@ -86,16 +89,73 @@ namespace WandEnhancer.Core
{
new PatchEntry
{
Name = "devToolsBeforeInputEvent",
// Anchor on the Electron main-process `<app>.whenReady().then(`
// call. This site is far more stable than the minified renderer
// keydown listener that previously held the F12 -> ACTION_OPEN_DEV_TOOLS
// dispatch (its identifiers and shape change on every Wand release).
// We attach a `before-input-event` hook to every BrowserWindow's
// webContents which toggles DevTools on F12 directly from the main
// process, bypassing the renderer dispatcher entirely.
Target = new Regex(@"(?<app>\w+)\.whenReady\(\)\.then\("),
Patch = "${app}.on(\"browser-window-created\",((_,w)=>{try{w.webContents.on(\"before-input-event\",((_,i)=>{if(\"F12\"===i.key&&\"keyDown\"===i.type){w.webContents.isDevToolsOpened()?w.webContents.closeDevTools():w.webContents.openDevTools({mode:\"detach\"})}}))}catch(e){}})),${app}.whenReady().then("
}
}
},
{
EPatchType.RemoteWebPanelPreview,
new[]
{
new PatchEntry
{
Name = "remoteBridgeMainBoot",
Target = new Regex(@"(?<app>\w+)\.whenReady\(\)\.then\(run\)"),
Patch = "${app}.whenReady().then(()=>{try{const p=require(\"node:path\");require(p.join(__dirname,\"remote-panel\",\"bridge.cjs\")).installWandRuntime(require(\"electron\"));}catch(e){try{const fs=require(\"node:fs\"),os=require(\"node:os\"),p=require(\"node:path\");fs.appendFileSync(p.join(os.tmpdir(),\"wand-remote-bridge.log\"),\"[\"+new Date().toISOString()+\"] [boot-error] \"+(e&&e.stack||e)+\"\\n\");}catch(_){}}return run()})"
},
new PatchEntry
{
Name = "remoteBridgeReset",
Target = new Regex(@"#Je\(\)\{this\.#Oe&&\(this\.#Oe\.dispose\(\),this\.#Oe=null\),this\.#Pe=Date\.now\(\)\.toString\(\),this\.#ke=null,this\.#_e=\[],this\.#Ee=null\}"),
Patch = "#Je(){this.#Oe&&(this.#Oe.dispose(),this.#Oe=null),this.#Pe=Date.now().toString(),this.#ke=null,this.#_e=[],this.#Ee=null,this.__wandRemoteTrainerInfo=null,this.__wandRemoteBridge?.sync(null)}"
},
new PatchEntry
{
Name = "remoteBridgeSyncSnapshot",
Target = new Regex(@"#Be\(\)\{if\(this\.status===i\.Connected\)\{let e,t=!1,s=this\.#Ee\?\.getMetadata\(h\.vO\)\?\.gameVersion\?\?null,i=!1;const n=this\.#Ve\[this\.#ke\?\?""""\]\|\|null;this\.#Re&&\(e=this\.#Ae\.getPreferredInstallationInfo\(this\.#Re\),e\.app&&\(t=!0,s\?\?=e\.version\?\?null,i=""number""==typeof e\.version&&!this\.#_e\.includes\(e\.version\)\)\),this\.#Me\?\.send\(""client-state"",\{instanceId:this\.#Pe,trainerId:this\.#ke,trainerLoading:this\.#Ee\?\.isLoading\(\),gameInstalled:t,gameVersion:s,needsCompatibilityWarning:i,values:this\.#Ke\(\),themeId:this\.#We,settings:R\(this\.settings\),language:this\.#Ne,accountUuid:this\.account\.uuid,notesReadHash:n,isTimeLimitExpired:""expired""===this\.#Fe\.timerState\}\)\}\}"),
Patch = "#Be(){let e,t=!1,s=this.#Ee?.getMetadata(h.vO)?.gameVersion??null,o=!1;const n=this.#Ve[this.#ke??\"\"]||null;this.#Re&&(e=this.#Ae.getPreferredInstallationInfo(this.#Re),e.app&&(t=!0,s??=e.version??null,o=\"number\"==typeof e.version&&!this.#_e.includes(e.version)));this.status===i.Connected&&this.#Me?.send(\"client-state\",{instanceId:this.#Pe,trainerId:this.#ke,trainerLoading:this.#Ee?.isLoading(),gameInstalled:t,gameVersion:s,needsCompatibilityWarning:o,values:this.#Ke(),themeId:this.#We,settings:R(this.settings),language:this.#Ne,accountUuid:this.account.uuid,notesReadHash:n,isTimeLimitExpired:\"expired\"===this.#Fe.timerState});this.__wandRemoteBridge?.sync({instanceId:this.#Pe,trainerId:this.#ke,trainerInfo:this.__wandRemoteTrainerInfo??null,metadata:this.#Ee?.getMetadata(h.vO)??null,trainerLoading:this.#Ee?.isLoading()??false,gameInstalled:t,gameVersion:s,needsCompatibilityWarning:o,language:this.#Ne,themeId:this.#We,notesReadHash:n,isTimeLimitExpired:\"expired\"===this.#Fe.timerState,values:this.#Ke()})}"
},
new PatchEntry
{
Name = "remoteBridgeBindHandler",
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;"
},
new PatchEntry
{
Name = "remoteBridgeValueDelta",
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()}"
},
new PatchEntry
{
Name = "remoteTooltipPreviewUrl",
Target = new Regex(@"remoteUrl=""wemodwebsite://remote"""),
Patch = "remoteUrl=globalThis.__wandRemoteBridgeUrl||\"" + RemoteWebPanelFallbackUrl + "\""
},
new PatchEntry
{
Name = "remoteQrPreviewUrl",
Resolver = new ResolveContext
{
Handler = (matchContent) => {
var match = Regex.Match(matchContent, @"this\.#(\w+)\(""ACTION_OPEN_DEV_TOOLS""\)");
Handler = (matchContent) =>
{
var match = Regex.Match(matchContent, @"this\.canvasElement&&(\w+)\.mo");
return match.Success ? match.Groups[1].Value : null;
},
Placeholder = "<dispatch_method>"
Placeholder = "<qr_writer>"
},
Target = new Regex(@"document\.addEventListener\(""keydown"",\s*\((?<arg>\w+)\s*=>\s*\{[^}]*?""ACTION_OPEN_DEV_TOOLS""[^}]*?\}\)\)", RegexOptions.Singleline),
Patch = "document.addEventListener(\"keydown\",(${arg}=>{\"F12\"!==${arg}.key||this.#<dispatch_method>(\"ACTION_OPEN_DEV_TOOLS\")}))"
Target = new Regex(@"this\.canvasElement&&\w+\.mo\(this\.canvasElement,`\$\{\w+\.A\.wemodWebsiteUrl\}/remote`,this\.options\)"),
Patch = "this.canvasElement&&<qr_writer>.mo(this.canvasElement,globalThis.__wandRemoteBridgeUrl||\"" + RemoteWebPanelFallbackUrl + "\",this.options)"
}
}
}
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Quellcode</s:String>
<s:String x:Key="mw_made_by">Mit ❤️ von k1tbyte erstellt</s:String>
<s:String x:Key="mw_star_hint">Gib einen Stern, wenn dir das geholfen hat ;)</s:String>
<s:String x:Key="mw_copy_logs">Logs in die Zwischenablage kopieren</s:String>
<s:String x:Key="mw_export_logs">Logs in Datei exportieren</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">WeMod Pro aktivieren</s:String>
<s:String x:Key="pv_devtools">DevTools mit F12</s:String>
<s:String x:Key="pv_disable_updates">Updates deaktivieren</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Remote-Zugriff aktivieren (Beta)</s:String>
<s:String x:Key="pv_custom_scripts">Benutzerdefinierte Skripte</s:String>
<s:String x:Key="pv_add_js_scripts">.js hinzufügen</s:String>
<s:String x:Key="pv_custom_scripts_hint">Ausgewählte .js-Dateien werden in Wand gepackt und im Renderer geladen.</s:String>
<s:String x:Key="pv_no_custom_scripts">Keine Skripte ausgewählt</s:String>
<s:String x:Key="pv_start">Starten</s:String>
<s:String x:Key="pv_popup_title">Was werden wir verbessern?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Source code</s:String>
<s:String x:Key="mw_made_by">Made with ❤️ by k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Put a star if you found this helpful ;)</s:String>
<s:String x:Key="mw_copy_logs">Copy logs to clipboard</s:String>
<s:String x:Key="mw_export_logs">Export logs to file</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Activate WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools on F12</s:String>
<s:String x:Key="pv_disable_updates">Disable updates</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Enable remote access (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Custom scripts</s:String>
<s:String x:Key="pv_add_js_scripts">Add .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Selected .js files are packed into Wand and loaded in the renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">No scripts selected</s:String>
<s:String x:Key="pv_start">Start</s:String>
<s:String x:Key="pv_popup_title">What are we gonna enhance?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Código fuente</s:String>
<s:String x:Key="mw_made_by">Hecho con ❤️ por k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Pon una estrella si te fue útil ;)</s:String>
<s:String x:Key="mw_copy_logs">Copiar registros al portapapeles</s:String>
<s:String x:Key="mw_export_logs">Exportar registros a un archivo</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Activar WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools en F12</s:String>
<s:String x:Key="pv_disable_updates">Desactivar actualizaciones</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Habilitar acceso remoto (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Scripts personalizados</s:String>
<s:String x:Key="pv_add_js_scripts">Agregar .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Los archivos .js seleccionados se empaquetan en Wand y se cargan en el renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">No hay scripts seleccionados</s:String>
<s:String x:Key="pv_start">Iniciar</s:String>
<s:String x:Key="pv_popup_title">¿Qué vamos a mejorar?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Code source</s:String>
<s:String x:Key="mw_made_by">Fait avec ❤️ par k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Mettez une étoile si cela vous a aidé ;)</s:String>
<s:String x:Key="mw_copy_logs">Copier les logs dans le presse-papiers</s:String>
<s:String x:Key="mw_export_logs">Exporter les logs dans un fichier</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Activer WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools sur F12</s:String>
<s:String x:Key="pv_disable_updates">Désactiver les mises à jour</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Activer l'accès à distance (bêta)</s:String>
<s:String x:Key="pv_custom_scripts">Scripts personnalisés</s:String>
<s:String x:Key="pv_add_js_scripts">Ajouter .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Les fichiers .js sélectionnés sont intégrés dans Wand et chargés dans le renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Aucun script sélectionné</s:String>
<s:String x:Key="pv_start">Démarrer</s:String>
<s:String x:Key="pv_popup_title">Qu'allons-nous modifier ?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Codice sorgente</s:String>
<s:String x:Key="mw_made_by">Creato con ❤️ da k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Metti una stella se ti è stato utile ;)</s:String>
<s:String x:Key="mw_copy_logs">Copia i log negli appunti</s:String>
<s:String x:Key="mw_export_logs">Esporta i log su file</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Attiva WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools su F12</s:String>
<s:String x:Key="pv_disable_updates">Disattiva aggiornamenti</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Abilita accesso remoto (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Script personalizzati</s:String>
<s:String x:Key="pv_add_js_scripts">Aggiungi .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">I file .js selezionati vengono inseriti in Wand e caricati nel renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nessuno script selezionato</s:String>
<s:String x:Key="pv_start">Avvia</s:String>
<s:String x:Key="pv_popup_title">Cosa modificheremo?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">ソースコード</s:String>
<s:String x:Key="mw_made_by">k1tbyte が ❤️ を込めて作成</s:String>
<s:String x:Key="mw_star_hint">役に立ったらスターをつけてください ;)</s:String>
<s:String x:Key="mw_copy_logs">ログをクリップボードにコピー</s:String>
<s:String x:Key="mw_export_logs">ログをファイルにエクスポート</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">WeMod Pro を有効化</s:String>
<s:String x:Key="pv_devtools">F12でDevTools</s:String>
<s:String x:Key="pv_disable_updates">アップデートを無効化</s:String>
<s:String x:Key="pv_remote_web_panel_preview">リモートアクセスを有効化(ベータ)</s:String>
<s:String x:Key="pv_custom_scripts">カスタムスクリプト</s:String>
<s:String x:Key="pv_add_js_scripts">.js を追加</s:String>
<s:String x:Key="pv_custom_scripts_hint">選択した .js ファイルは Wand に組み込まれ、レンダラーで読み込まれます。</s:String>
<s:String x:Key="pv_no_custom_scripts">スクリプトが選択されていません</s:String>
<s:String x:Key="pv_start">開始</s:String>
<s:String x:Key="pv_popup_title">何を改善しますか?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Kod źródłowy</s:String>
<s:String x:Key="mw_made_by">Wykonane z ❤️ przez k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Daj gwiazdkę, jeśli ci pomogło ;)</s:String>
<s:String x:Key="mw_copy_logs">Skopiuj logi do schowka</s:String>
<s:String x:Key="mw_export_logs">Eksportuj logi do pliku</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Aktywuj WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools na F12</s:String>
<s:String x:Key="pv_disable_updates">Wyłącz aktualizacje</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Włącz zdalny dostęp (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Skrypty niestandardowe</s:String>
<s:String x:Key="pv_add_js_scripts">Dodaj .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Wybrane pliki .js są pakowane do Wand i ładowane w rendererze.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nie wybrano skryptów</s:String>
<s:String x:Key="pv_start">Rozpocznij</s:String>
<s:String x:Key="pv_popup_title">Co będziemy ulepszać?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Código fonte</s:String>
<s:String x:Key="mw_made_by">Feito com ❤️ por k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Dê uma estrela se isso te ajudou ;)</s:String>
<s:String x:Key="mw_copy_logs">Copiar logs para a área de transferência</s:String>
<s:String x:Key="mw_export_logs">Exportar logs para arquivo</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Ativar WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools no F12</s:String>
<s:String x:Key="pv_disable_updates">Desativar atualizações</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Ativar acesso remoto (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Scripts personalizados</s:String>
<s:String x:Key="pv_add_js_scripts">Adicionar .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Os arquivos .js selecionados são empacotados no Wand e carregados no renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Nenhum script selecionado</s:String>
<s:String x:Key="pv_start">Iniciar</s:String>
<s:String x:Key="pv_popup_title">O que vamos melhorar?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Исходный код</s:String>
<s:String x:Key="mw_made_by">Сделано с ❤️ by k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Поставьте звезду, если это было полезно ;)</s:String>
<s:String x:Key="mw_copy_logs">Скопировать логи в буфер обмена</s:String>
<s:String x:Key="mw_export_logs">Экспортировать логи в файл</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Активировать WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools на F12</s:String>
<s:String x:Key="pv_disable_updates">Отключить обновления</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Remote-доступ (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Свои скрипты</s:String>
<s:String x:Key="pv_add_js_scripts">Добавить .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Выбранные .js попадут в Wand и загрузятся в renderer.</s:String>
<s:String x:Key="pv_no_custom_scripts">Скрипты не выбраны</s:String>
<s:String x:Key="pv_start">Начать</s:String>
<s:String x:Key="pv_popup_title">Что будем улучшать?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Kaynak kodu</s:String>
<s:String x:Key="mw_made_by">k1tbyte tarafından ❤️ ile yapıldı</s:String>
<s:String x:Key="mw_star_hint">Yardımcı olduysa yıldız verin ;)</s:String>
<s:String x:Key="mw_copy_logs">Günlükleri panoya kopyala</s:String>
<s:String x:Key="mw_export_logs">Günlükleri dosyaya aktar</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">WeMod Pro'yu Etkinleştir</s:String>
<s:String x:Key="pv_devtools">F12 ile DevTools</s:String>
<s:String x:Key="pv_disable_updates">Güncellemeleri devre dışı bırak</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Uzaktan erişimi etkinleştir (beta)</s:String>
<s:String x:Key="pv_custom_scripts">Özel betikler</s:String>
<s:String x:Key="pv_add_js_scripts">.js ekle</s:String>
<s:String x:Key="pv_custom_scripts_hint">Seçilen .js dosyaları Wand içine paketlenir ve renderer'da yüklenir.</s:String>
<s:String x:Key="pv_no_custom_scripts">Betik seçilmedi</s:String>
<s:String x:Key="pv_start">Başlat</s:String>
<s:String x:Key="pv_popup_title">Neyi geliştireceğiz?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">Вихідний код</s:String>
<s:String x:Key="mw_made_by">Зроблено з ❤️ by k1tbyte</s:String>
<s:String x:Key="mw_star_hint">Поставте зірку, якщо це було корисно ;)</s:String>
<s:String x:Key="mw_copy_logs">Скопіювати логи до буфера обміну</s:String>
<s:String x:Key="mw_export_logs">Експортувати логи у файл</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">Активувати WeMod Pro</s:String>
<s:String x:Key="pv_devtools">DevTools на F12</s:String>
<s:String x:Key="pv_disable_updates">Вимкнути оновлення</s:String>
<s:String x:Key="pv_remote_web_panel_preview">Увімкнути віддалений доступ (бета)</s:String>
<s:String x:Key="pv_custom_scripts">Користувацькі скрипти</s:String>
<s:String x:Key="pv_add_js_scripts">Додати .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">Вибрані файли .js пакуються у Wand і завантажуються в рендерері.</s:String>
<s:String x:Key="pv_no_custom_scripts">Скрипти не вибрано</s:String>
<s:String x:Key="pv_start">Почати</s:String>
<s:String x:Key="pv_popup_title">Що будемо покращувати?</s:String>
<!--#endregion -->
+7
View File
@@ -16,6 +16,8 @@
<s:String x:Key="mw_source_code">源代码</s:String>
<s:String x:Key="mw_made_by">由 k1tbyte 用 ❤️ 制作</s:String>
<s:String x:Key="mw_star_hint">如果这对您有帮助,请给个星标 ;)</s:String>
<s:String x:Key="mw_copy_logs">复制日志到剪贴板</s:String>
<s:String x:Key="mw_export_logs">将日志导出到文件</s:String>
<!--#endregion -->
<!--#region Settings -->
@@ -28,6 +30,11 @@
<s:String x:Key="pv_activate_pro">激活 WeMod Pro</s:String>
<s:String x:Key="pv_devtools">按 F12 打开开发者工具</s:String>
<s:String x:Key="pv_disable_updates">禁用更新</s:String>
<s:String x:Key="pv_remote_web_panel_preview">启用远程访问(beta</s:String>
<s:String x:Key="pv_custom_scripts">自定义脚本</s:String>
<s:String x:Key="pv_add_js_scripts">添加 .js</s:String>
<s:String x:Key="pv_custom_scripts_hint">选中的 .js 文件会打包到 Wand 并在渲染器中加载。</s:String>
<s:String x:Key="pv_no_custom_scripts">未选择脚本</s:String>
<s:String x:Key="pv_start">开始</s:String>
<s:String x:Key="pv_popup_title">我们要增强什么?</s:String>
<!--#endregion -->
+4 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using WandEnhancer.Utils;
@@ -12,13 +11,16 @@ namespace WandEnhancer.Models
ActivatePro = 1,
DisableUpdates = 2,
DisableTelemetry = 4,
DevToolsOnF12 = 8
DevToolsOnF12 = 8,
RemoteWebPanelPreview = 16
}
public sealed class PatchConfig
{
private string _path;
public HashSet<EPatchType> PatchTypes { get; set; }
public List<string> CustomScriptPaths { get; set; } = new List<string>();
public bool AutoApplyPatches { get; set; }
+2 -2
View File
@@ -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.6.0")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyVersion("1.0.7.0")]
[assembly: AssemblyFileVersion("1.0.7.0")]
+8
View File
@@ -27,6 +27,14 @@
<Geometry x:Key="ArrowLeft">
M5.05 11.94l5-5v3.99H19l-.03 2.01H10.05v4Z
</Geometry>
<Geometry x:Key="CopyIcon">
M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1Z
</Geometry>
<Geometry x:Key="ExportIcon">
M14 13h-3v3H9v-3H6v-2h3V8h2v3h3m-1-9H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9l-7-7M5 4h7v5h5v11H5V4Z
</Geometry>
<!--<Geometry x:Key="">
+16 -1
View File
@@ -109,7 +109,8 @@
<Border Grid.Row="1" BorderBrush="{DynamicResource Border}" BorderThickness="1"
Margin="10 0 10 10"
CornerRadius="5">
<ListBox ItemsSource="{Binding LogList}" SelectionMode="Single"
<Grid>
<ListBox ItemsSource="{Binding LogList}" SelectionMode="Single"
BorderBrush="Transparent" BorderThickness="0"
Background="Transparent"
x:Name="LogList"
@@ -152,6 +153,20 @@
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top"
Margin="0 4 4 0">
<Button Width="22" Height="22" Padding="4"
Style="{StaticResource IconButton}"
Tag="{StaticResource CopyIcon}"
ToolTip="{DynamicResource mw_copy_logs}"
Command="{Binding CopyLogsCommand}"/>
<Button Width="22" Height="22" Padding="4" Margin="4 0 0 0"
Style="{StaticResource IconButton}"
Tag="{StaticResource ExportIcon}"
ToolTip="{DynamicResource mw_export_logs}"
Command="{Binding ExportLogsCommand}"/>
</StackPanel>
</Grid>
</Border>
@@ -2,6 +2,7 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WandEnhancer.Core;
@@ -73,6 +74,8 @@ namespace WandEnhancer.View.MainWindow
public RelayCommand RestoreBackupCommand { get; }
public RelayCommand UpdateCommand { get; }
public RelayCommand OpenSettingsCommand { get; }
public RelayCommand CopyLogsCommand { get; }
public RelayCommand ExportLogsCommand { get; }
private void OnFolderPathSelection(object obj)
{
@@ -208,6 +211,64 @@ namespace WandEnhancer.View.MainWindow
MainWindow.Instance.OpenPopup(new SettingsPopup(), Application.Current.FindResource("settings_title") as string);
}
private string BuildLogReport()
{
var builder = new StringBuilder();
foreach (var entry in LogList)
{
builder.AppendLine(entry.Message);
}
return builder.ToString();
}
private void OnCopyLogs(object param)
{
if (LogList.Count == 0)
{
return;
}
try
{
System.Windows.Clipboard.SetText(BuildLogReport());
Log("Logs copied to clipboard.", ELogType.Success);
}
catch (Exception e)
{
Log($"Failed to copy logs: {e.Message}", ELogType.Error);
}
}
private void OnExportLogs(object param)
{
if (LogList.Count == 0)
{
return;
}
using (var dialog = new SaveFileDialog
{
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*",
FileName = $"wand-enhancer-log-{DateTime.Now:yyyyMMdd-HHmmss}.txt"
})
{
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
File.WriteAllText(dialog.FileName, BuildLogReport());
Log($"Logs exported to '{dialog.FileName}'.", ELogType.Success);
}
catch (Exception e)
{
Log($"Failed to export logs: {e.Message}", ELogType.Error);
}
}
}
public MainWindowVm(MainWindow view)
{
Task.Run(async () => IsUpdateAvailable = await _updater.CheckForUpdates());
@@ -217,6 +278,8 @@ namespace WandEnhancer.View.MainWindow
RestoreBackupCommand = new RelayCommand(OnBackupRestoring);
UpdateCommand = new RelayCommand(OnUpdate);
OpenSettingsCommand = new RelayCommand(OnOpenSettings);
CopyLogsCommand = new RelayCommand(OnCopyLogs);
ExportLogsCommand = new RelayCommand(OnExportLogs);
WeModInfo = Extensions.FindWeMod();
if (WeModInfo == null)
+68 -16
View File
@@ -11,35 +11,87 @@
Foreground="{DynamicResource MutedForeground}"
FontWeight="Medium"
FontSize="13">
<Grid>
<Grid MinWidth="430">
<Grid Visibility="Visible" Margin="0 0 5 0">
<Grid.RowDefinitions>
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" VerticalAlignment="Center" Text="{DynamicResource pv_activate_pro}" />
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_activate_pro}" />
<CheckBox Grid.Row="0" Grid.Column="1" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="True" />
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="{DynamicResource pv_devtools}" />
<CheckBox Grid.Row="1" x:Name="DevToolsHotkeyBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_devtools}" />
<CheckBox Grid.Row="1" Grid.Column="1" x:Name="DevToolsHotkeyBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="{DynamicResource pv_disable_updates}" />
<CheckBox Grid.Row="2" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_disable_updates}" />
<CheckBox Grid.Row="2" Grid.Column="1" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<!--<TextBlock
ToolTip="Disable if you want to use older versions separately and manage versions manually via different shortcuts"
ToolTipService.InitialShowDelay="300"
Grid.Row="3" VerticalAlignment="Center">
Apply the patch to new versions <LineBreak /> automatically (hover to see more)
</TextBlock>
<CheckBox Grid.Row="3" x:Name="AutoUpdates" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="True" />-->
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Text="{DynamicResource pv_remote_web_panel_preview}" />
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="RemoteWebPanelPreviewBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<Button Grid.Row="3" Padding="0 5 0 5" Margin="0 15 0 0" Content="{DynamicResource pv_start}"
<Border Grid.Row="4" Grid.ColumnSpan="2" Margin="0 14 0 0" Padding="10"
BorderBrush="{DynamicResource Border}" BorderThickness="1" CornerRadius="4"
Background="{DynamicResource Muted}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center"
Foreground="{DynamicResource Foreground}"
Text="{DynamicResource pv_custom_scripts}" />
<Button Grid.Column="1" Padding="10 4" Content="{DynamicResource pv_add_js_scripts}"
Click="OnAddScriptClick" />
</Grid>
<TextBlock Margin="0 7 0 0" FontSize="11" TextWrapping="Wrap"
Opacity="0.8"
Text="{DynamicResource pv_custom_scripts_hint}" />
<ItemsControl x:Name="ScriptList" Margin="0 2 0 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0 6 6 0" Padding="8 3"
Background="{DynamicResource Card}"
BorderBrush="{DynamicResource Border}"
BorderThickness="1"
CornerRadius="3">
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Right" Tag="{Binding}" Content="x"
BorderThickness="0" Padding="6 0" Margin="6 0 0 0"
Click="OnRemoveScriptClick" />
<TextBlock VerticalAlignment="Center"
Foreground="{DynamicResource Foreground}"
Text="{Binding FileName}" />
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock x:Name="NoScriptsText" Margin="0 7 0 0" FontSize="11"
Opacity="0.7"
Text="{DynamicResource pv_no_custom_scripts}" />
</StackPanel>
</Border>
<Button Grid.Row="5" Grid.ColumnSpan="2" Padding="0 5 0 5" Margin="0 15 0 0" Content="{DynamicResource pv_start}"
Click="OnPatchButtonClick" />
</Grid>
</Grid>
@@ -1,26 +1,75 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using WandEnhancer.Models;
using WandEnhancer.View.Controls;
namespace WandEnhancer.View.Popups
{
public partial class PatchVectorsPopup : UserControl
{
private const string JavaScriptDialogFilter = "JavaScript files (*.js)|*.js";
private const string JavaScriptFileExtension = ".js";
private readonly Action<PatchConfig> _onApply;
private readonly ObservableCollection<SelectedScript> _selectedScripts = new ObservableCollection<SelectedScript>();
public PatchVectorsPopup(Action<PatchConfig> onApply)
{
_onApply = onApply;
InitializeComponent();
ScriptList.ItemsSource = _selectedScripts;
UpdateScriptsEmptyState();
}
private void OnAddScriptClick(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
{
Filter = JavaScriptDialogFilter,
Multiselect = true,
CheckFileExists = true
};
if (dialog.ShowDialog() != true)
{
return;
}
foreach (var path in dialog.FileNames.Where(IsJavaScriptFile))
{
AddScript(path);
}
if (_selectedScripts.Count > 0)
{
RemoteWebPanelPreviewBox.IsChecked = true;
}
UpdateScriptsEmptyState();
}
private void OnRemoveScriptClick(object sender, RoutedEventArgs e)
{
var button = sender as Button;
var script = button?.Tag as SelectedScript;
if (script == null)
{
return;
}
_selectedScripts.Remove(script);
UpdateScriptsEmptyState();
}
private void OnPatchButtonClick(object sender, RoutedEventArgs e)
{
if (ActivateProBox.IsChecked != true && DisableUpdateBox.IsChecked != true &&
DevToolsHotkeyBox.IsChecked != true)
DevToolsHotkeyBox.IsChecked != true && RemoteWebPanelPreviewBox.IsChecked != true)
{
return;
}
@@ -41,11 +90,51 @@ namespace WandEnhancer.View.Popups
result.Add(EPatchType.DevToolsOnF12);
}
if (RemoteWebPanelPreviewBox.IsChecked == true)
{
result.Add(EPatchType.RemoteWebPanelPreview);
}
_onApply(new PatchConfig
{
PatchTypes = result,
AutoApplyPatches =/* AutoUpdates.IsChecked == true*/ false
CustomScriptPaths = _selectedScripts.Select(script => script.FullPath).ToList(),
AutoApplyPatches = false
});
}
private void AddScript(string path)
{
var fullPath = Path.GetFullPath(path);
if (_selectedScripts.Any(script => string.Equals(script.FullPath, fullPath, StringComparison.OrdinalIgnoreCase)))
{
return;
}
_selectedScripts.Add(new SelectedScript(fullPath));
}
private static bool IsJavaScriptFile(string path)
{
return File.Exists(path) && string.Equals(Path.GetExtension(path), JavaScriptFileExtension, StringComparison.OrdinalIgnoreCase);
}
private void UpdateScriptsEmptyState()
{
NoScriptsText.Visibility = _selectedScripts.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
private sealed class SelectedScript
{
public SelectedScript(string fullPath)
{
FullPath = fullPath;
FileName = Path.GetFileName(fullPath);
}
public string FullPath { get; }
public string FileName { get; }
}
}
}
+11
View File
@@ -160,6 +160,17 @@
<LogicalName>proxydll</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\web-panel\dist\**\*.*" Condition="Exists('..\web-panel\dist\index.html')">
<LogicalName>remote-panel/dist/%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="..\web-panel\bridge\wand-remote-bridge.cjs" Condition="Exists('..\web-panel\bridge\wand-remote-bridge.cjs')">
<LogicalName>remote-panel/bridge.cjs</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="..\web-panel\scripts\default\*.js" Condition="Exists('..\web-panel\scripts\default')">
<LogicalName>remote-panel/renderer-scripts/%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">