diff --git a/src/Models/SimulateConfigModel.cs b/src/Models/SimulateConfigModel.cs
new file mode 100644
index 0000000..5e4c804
--- /dev/null
+++ b/src/Models/SimulateConfigModel.cs
@@ -0,0 +1,36 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace GeneralUpdate.Tools.Models;
+
+///
+/// Configuration for the simulate-update module.
+///
+public partial class SimulateConfigModel : ObservableObject
+{
+ /// User-provided old-version app directory to test against.
+ [ObservableProperty] private string _appDirectory = string.Empty;
+
+ /// Path to a patch .zip generated by the Patch module.
+ [ObservableProperty] private string _patchFilePath = string.Empty;
+
+ /// The current version of the app being tested.
+ [ObservableProperty] private string _currentVersion = "1.0.0.0";
+
+ /// The target version the patch upgrades to.
+ [ObservableProperty] private string _targetVersion = "2.0.0.0";
+
+ /// Platform selector (1=Windows, 2=Linux).
+ [ObservableProperty] private int _platform = 1;
+
+ /// AppType sent to the server (1=ClientApp, 2=UpgradeApp).
+ [ObservableProperty] private int _appType = 1;
+
+ /// Application secret key for the update API.
+ [ObservableProperty] private string _appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6";
+
+ /// Product identifier.
+ [ObservableProperty] private string _productId = "2d974e2a-31e6-4887-9bb1-b4689e98c77a";
+
+ /// Directory where client.cs / upgrade.cs and server are generated.
+ [ObservableProperty] private string _outputDirectory = string.Empty;
+}
diff --git a/src/Services/LocalizationService.cs b/src/Services/LocalizationService.cs
index 416a402..3717eb7 100644
--- a/src/Services/LocalizationService.cs
+++ b/src/Services/LocalizationService.cs
@@ -115,6 +115,27 @@ public string this[string key]
["Theme.Light"] = "浅色",
["Theme.Dark"] = "深色",
["Theme.Toggle"] = "切换主题",
+ ["Nav.Simulate"] = "模拟更新",
+ ["Sim.Title"] = "模拟更新",
+ ["Sim.TestTarget"] = "测试目标",
+ ["Sim.OldAppDir"] = "旧版本应用目录",
+ ["Sim.PatchFile"] = "补丁包文件",
+ ["Sim.Select"] = "选择",
+ ["Sim.UpdateConfig"] = "更新配置",
+ ["Sim.CurrentVer"] = "当前版本",
+ ["Sim.TargetVer"] = "目标版本",
+ ["Sim.Platform"] = "平台",
+ ["Sim.AppType"] = "应用类型",
+ ["Sim.AppSecret"] = "应用密钥",
+ ["Sim.ProductId"] = "产品ID",
+ ["Sim.Output"] = "输出",
+ ["Sim.OutputDir"] = "模拟目录",
+ ["Sim.Start"] = "开始模拟",
+ ["Sim.SelectAppDir"] = "选择旧版本应用目录",
+ ["Sim.SelectPatch"] = "选择补丁包",
+ ["Sim.SelectOutput"] = "选择模拟输出目录",
+ ["Sim.ValidateDirs"] = "请填写所有必填项",
+ ["Sim.DotnetCheck"] = "需要 .NET 10.0 SDK,请先安装",
},
["en-US"] = new()
{
@@ -187,6 +208,27 @@ public string this[string key]
["Theme.Light"] = "Light",
["Theme.Dark"] = "Dark",
["Theme.Toggle"] = "Toggle Theme",
+ ["Nav.Simulate"] = "Simulate",
+ ["Sim.Title"] = "Simulate Update",
+ ["Sim.TestTarget"] = "Test Target",
+ ["Sim.OldAppDir"] = "Old App Directory",
+ ["Sim.PatchFile"] = "Patch Package",
+ ["Sim.Select"] = "Select",
+ ["Sim.UpdateConfig"] = "Update Config",
+ ["Sim.CurrentVer"] = "Current Version",
+ ["Sim.TargetVer"] = "Target Version",
+ ["Sim.Platform"] = "Platform",
+ ["Sim.AppType"] = "App Type",
+ ["Sim.AppSecret"] = "App Secret",
+ ["Sim.ProductId"] = "Product ID",
+ ["Sim.Output"] = "Output",
+ ["Sim.OutputDir"] = "Simulate Directory",
+ ["Sim.Start"] = "Start Simulation",
+ ["Sim.SelectAppDir"] = "Select old version directory",
+ ["Sim.SelectPatch"] = "Select patch package",
+ ["Sim.SelectOutput"] = "Select simulation output directory",
+ ["Sim.ValidateDirs"] = "Please fill in all required fields",
+ ["Sim.DotnetCheck"] = ".NET 10.0 SDK is required. Please install it first.",
}
};
diff --git a/src/ViewModels/MainWindowViewModel.cs b/src/ViewModels/MainWindowViewModel.cs
index df0e4cb..d0786ba 100644
--- a/src/ViewModels/MainWindowViewModel.cs
+++ b/src/ViewModels/MainWindowViewModel.cs
@@ -35,6 +35,7 @@ private void SyncNavItems()
NavItems.Add(new("Patch", _loc["Nav.Patch"], typeof(PatchViewModel), true));
NavItems.Add(new("Extension", _loc["Nav.Extension"], typeof(ExtensionViewModel), false));
NavItems.Add(new("OSS", _loc["Nav.OSS"], typeof(OSSViewModel), false));
+ NavItems.Add(new("Simulate", _loc["Nav.Simulate"], typeof(SimulateViewModel), false));
}
[RelayCommand]
@@ -46,7 +47,8 @@ private void Navigate(NavItem item)
{
"Patch" => new PatchViewModel(),
"Extension" => new ExtensionViewModel(),
- _ => new OSSViewModel()
+ "OSS" => new OSSViewModel(),
+ _ => new SimulateViewModel()
};
}
diff --git a/src/ViewModels/SimulateViewModel.cs b/src/ViewModels/SimulateViewModel.cs
new file mode 100644
index 0000000..211be8d
--- /dev/null
+++ b/src/ViewModels/SimulateViewModel.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using GeneralUpdate.Tools.Models;
+using GeneralUpdate.Tools.Services;
+
+namespace GeneralUpdate.Tools.ViewModels;
+
+public partial class SimulateViewModel : ViewModelBase
+{
+ private readonly LocalizationService _loc = LocalizationService.Instance;
+
+ public SimulateConfigModel Config { get; } = new();
+
+ [ObservableProperty] private bool _isRunning;
+ [ObservableProperty] private string _status;
+ [ObservableProperty] private ObservableCollection _log = new();
+
+ public ObservableCollection Platforms { get; } = new()
+ {
+ new(1, "Windows"),
+ new(2, "Linux")
+ };
+
+ public ObservableCollection AppTypes { get; } = new()
+ {
+ new(1, "ClientApp"),
+ new(2, "UpgradeApp")
+ };
+
+ public SimulateViewModel()
+ {
+ _status = _loc["Patch.Ready"];
+ }
+
+ async Task PickFolder(string title)
+ {
+ var tl = Avalonia.Controls.TopLevel.GetTopLevel(
+ (Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow);
+ if (tl == null) return null;
+ var r = await tl.StorageProvider.OpenFolderPickerAsync(
+ new Avalonia.Platform.Storage.FolderPickerOpenOptions { Title = title, AllowMultiple = false });
+ return r.Count > 0 ? r[0].Path.LocalPath : null;
+ }
+
+ async Task PickFile(string title)
+ {
+ var tl = Avalonia.Controls.TopLevel.GetTopLevel(
+ (Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow);
+ if (tl == null) return null;
+ var r = await tl.StorageProvider.OpenFilePickerAsync(
+ new Avalonia.Platform.Storage.FilePickerOpenOptions { Title = title, AllowMultiple = false });
+ return r.Count > 0 ? r[0].Path.LocalPath : null;
+ }
+
+ [RelayCommand] async Task SelectAppDir() { var p = await PickFolder("选择旧版本应用目录"); if (p != null) Config.AppDirectory = p; }
+ [RelayCommand] async Task SelectPatch() { var p = await PickFile("选择补丁包"); if (p != null) Config.PatchFilePath = p; }
+ [RelayCommand] async Task SelectOutputDir() { var p = await PickFolder("选择模拟输出目录"); if (p != null) Config.OutputDirectory = p; }
+
+ [RelayCommand]
+ async Task StartSimulation()
+ {
+ // Validation will be implemented in issue #4
+ if (string.IsNullOrWhiteSpace(Config.AppDirectory)) { Status = "请选择旧版本应用目录"; return; }
+ if (string.IsNullOrWhiteSpace(Config.PatchFilePath)) { Status = "请选择补丁包文件"; return; }
+ if (string.IsNullOrWhiteSpace(Config.OutputDirectory)) { Status = "请选择模拟输出目录"; return; }
+ Status = "模拟功能将在后续 issue 中实现";
+ }
+
+ void L(string msg) => Log.Add($"[{DateTime.Now:HH:mm:ss}] {msg}");
+}
+
+public record PlatformItem(int Value, string DisplayName) { public override string ToString() => DisplayName; }
+public record AppTypeItem(int Value, string DisplayName) { public override string ToString() => DisplayName; }
diff --git a/src/Views/SimulateView.axaml b/src/Views/SimulateView.axaml
new file mode 100644
index 0000000..aa2dc5d
--- /dev/null
+++ b/src/Views/SimulateView.axaml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/SimulateView.axaml.cs b/src/Views/SimulateView.axaml.cs
new file mode 100644
index 0000000..3738c1c
--- /dev/null
+++ b/src/Views/SimulateView.axaml.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls;
+
+namespace GeneralUpdate.Tools.Views;
+
+public partial class SimulateView : UserControl
+{
+ public SimulateView() => InitializeComponent();
+}