diff --git a/src/Common/CsprojReader.cs b/src/Common/CsprojReader.cs
new file mode 100644
index 0000000..f21c084
--- /dev/null
+++ b/src/Common/CsprojReader.cs
@@ -0,0 +1,200 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace GeneralUpdate.Tool.Avalonia.Common;
+
+///
+/// Utility class for reading .csproj files
+///
+public static class CsprojReader
+{
+ ///
+ /// Read MainAppName from .csproj file
+ ///
+ public static string ReadMainAppName(string releaseDirectory)
+ {
+ try
+ {
+ var csprojFile = FindCsprojFile(releaseDirectory);
+ if (string.IsNullOrEmpty(csprojFile))
+ return string.Empty;
+
+ var doc = XDocument.Load(csprojFile);
+ var outputType = GetElementValue(doc, "OutputType");
+
+ // Check if OutputType contains WinExe/Exe (case-insensitive)
+ if (string.IsNullOrEmpty(outputType) ||
+ (!outputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase) &&
+ !outputType.Equals("Exe", StringComparison.OrdinalIgnoreCase)))
+ {
+ return string.Empty;
+ }
+
+ // Extract .csproj filename without extension
+ var projectName = Path.GetFileNameWithoutExtension(csprojFile);
+
+ // Search for matching .exe file recursively
+ var exeFile = FindExeFile(releaseDirectory, projectName);
+ if (!string.IsNullOrEmpty(exeFile))
+ {
+ return Path.GetFileNameWithoutExtension(exeFile);
+ }
+
+ // Fallback to AssemblyName or OutputName
+ var assemblyName = GetElementValue(doc, "AssemblyName");
+ if (!string.IsNullOrEmpty(assemblyName))
+ return assemblyName;
+
+ var outputName = GetElementValue(doc, "OutputName");
+ if (!string.IsNullOrEmpty(outputName))
+ return outputName;
+
+ return string.Empty;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error reading MainAppName: {ex.Message}");
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Read ClientVersion from .csproj file or .exe file version
+ ///
+ public static string ReadClientVersion(string releaseDirectory)
+ {
+ try
+ {
+ var csprojFile = FindCsprojFile(releaseDirectory);
+ if (string.IsNullOrEmpty(csprojFile))
+ return string.Empty;
+
+ var doc = XDocument.Load(csprojFile);
+
+ // Try to read Version tag
+ var version = GetElementValue(doc, "Version");
+ if (!string.IsNullOrEmpty(version))
+ return version;
+
+ // Fallback to .exe file version
+ var projectName = Path.GetFileNameWithoutExtension(csprojFile);
+ var exeFile = FindExeFile(releaseDirectory, projectName);
+
+ if (!string.IsNullOrEmpty(exeFile) && File.Exists(exeFile))
+ {
+ var versionInfo = FileVersionInfo.GetVersionInfo(exeFile);
+ if (!string.IsNullOrEmpty(versionInfo.FileVersion))
+ return versionInfo.FileVersion;
+ }
+
+ return string.Empty;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error reading ClientVersion: {ex.Message}");
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Read OutputPath from .csproj file
+ ///
+ public static string ReadOutputPath(string releaseDirectory)
+ {
+ try
+ {
+ var csprojFile = FindCsprojFile(releaseDirectory);
+ if (string.IsNullOrEmpty(csprojFile))
+ return string.Empty;
+
+ var doc = XDocument.Load(csprojFile);
+ var outputPath = GetElementValue(doc, "OutputPath");
+
+ return outputPath ?? string.Empty;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error reading OutputPath: {ex.Message}");
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Find .csproj file in the directory
+ ///
+ private static string FindCsprojFile(string directory)
+ {
+ if (!Directory.Exists(directory))
+ return string.Empty;
+
+ var csprojFiles = Directory.GetFiles(directory, "*.csproj", SearchOption.TopDirectoryOnly);
+
+ if (csprojFiles.Length == 0)
+ return string.Empty;
+
+ if (csprojFiles.Length > 1)
+ {
+ Trace.WriteLine($"Warning: Multiple .csproj files found in {directory}. Using the first one: {csprojFiles[0]}");
+ }
+
+ return csprojFiles[0];
+ }
+
+ ///
+ /// Find .exe file with matching name recursively
+ /// Note: Uses SearchOption.AllDirectories which may be slow for large directory trees.
+ /// This is acceptable as release directories are typically small.
+ ///
+ private static string FindExeFile(string directory, string baseName)
+ {
+ if (!Directory.Exists(directory))
+ return string.Empty;
+
+ try
+ {
+ // First try to find .exe file (Windows)
+ var exeFiles = Directory.GetFiles(directory, $"{baseName}.exe", SearchOption.AllDirectories);
+ if (exeFiles.Any())
+ return exeFiles.First();
+
+ // Then try to find executable without extension (Linux/Mac)
+ var allFiles = Directory.GetFiles(directory, baseName, SearchOption.AllDirectories);
+ foreach (var file in allFiles)
+ {
+ var fileInfo = new FileInfo(file);
+ // Check if file is executable (on Unix systems) or if it's an exact match
+ if (fileInfo.Name == baseName)
+ return file;
+ }
+
+ return string.Empty;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error searching for exe file: {ex.Message}");
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Get element value from XDocument
+ ///
+ private static string GetElementValue(XDocument doc, string elementName)
+ {
+ try
+ {
+ // Search in all PropertyGroup elements
+ var elements = doc.Descendants()
+ .Where(e => e.Name.LocalName == elementName);
+
+ return elements.FirstOrDefault()?.Value?.Trim() ?? string.Empty;
+ }
+ catch
+ {
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/Models/PacketConfigModel.cs b/src/Models/PacketConfigModel.cs
index 33ffb58..3333195 100644
--- a/src/Models/PacketConfigModel.cs
+++ b/src/Models/PacketConfigModel.cs
@@ -5,6 +5,7 @@ namespace GeneralUpdate.Tool.Avalonia.Models;
public class PacketConfigModel : ObservableObject
{
private string _appDirectory, _releaseDirectory, _patchDirectory, _name, _path, _driverDirectory;
+ private string _reportUrl, _updateUrl, _appName, _mainAppName, _clientVersion;
private PlatformModel _platform;
private FormatModel _format;
private EncodingModel _encoding;
@@ -109,4 +110,69 @@ public string DriverDirectory
OnPropertyChanged(nameof(DriverDirectory));
}
}
+
+ ///
+ /// 报告地址
+ ///
+ public string ReportUrl
+ {
+ get => _reportUrl;
+ set
+ {
+ _reportUrl = value;
+ OnPropertyChanged(nameof(ReportUrl));
+ }
+ }
+
+ ///
+ /// 更新地址
+ ///
+ public string UpdateUrl
+ {
+ get => _updateUrl;
+ set
+ {
+ _updateUrl = value;
+ OnPropertyChanged(nameof(UpdateUrl));
+ }
+ }
+
+ ///
+ /// 应用程序名称
+ ///
+ public string AppName
+ {
+ get => _appName;
+ set
+ {
+ _appName = value;
+ OnPropertyChanged(nameof(AppName));
+ }
+ }
+
+ ///
+ /// 主应用程序名称
+ ///
+ public string MainAppName
+ {
+ get => _mainAppName;
+ set
+ {
+ _mainAppName = value;
+ OnPropertyChanged(nameof(MainAppName));
+ }
+ }
+
+ ///
+ /// 客户端版本
+ ///
+ public string ClientVersion
+ {
+ get => _clientVersion;
+ set
+ {
+ _clientVersion = value;
+ OnPropertyChanged(nameof(ClientVersion));
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ViewModels/PacketViewModel.cs b/src/ViewModels/PacketViewModel.cs
index 762d1bb..fc50e9e 100644
--- a/src/ViewModels/PacketViewModel.cs
+++ b/src/ViewModels/PacketViewModel.cs
@@ -9,8 +9,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GeneralUpdate.Common.Compress;
+using GeneralUpdate.Common.Shared.Object;
using GeneralUpdate.Differential;
+using GeneralUpdate.Tool.Avalonia.Common;
using GeneralUpdate.Tool.Avalonia.Models;
+using Newtonsoft.Json;
using Nlnet.Avalonia.Controls;
namespace GeneralUpdate.Tool.Avalonia.ViewModels;
@@ -94,6 +97,11 @@ private void ResetAction()
ConfigModel.AppDirectory = GetPlatformSpecificPath();
ConfigModel.PatchDirectory = GetPlatformSpecificPath();
ConfigModel.DriverDirectory = string.Empty;
+ ConfigModel.ReportUrl = string.Empty;
+ ConfigModel.UpdateUrl = string.Empty;
+ ConfigModel.AppName = string.Empty;
+ ConfigModel.MainAppName = string.Empty;
+ ConfigModel.ClientVersion = string.Empty;
ConfigModel.Encoding = Encodings.First();
ConfigModel.Format = Formats.First();
}
@@ -142,6 +150,13 @@ private async Task BuildPacketAction()
{
try
{
+ // Validate required fields
+ if (!await ValidateRequiredFields())
+ return;
+
+ // Read configuration from .csproj
+ ReadProjectConfiguration();
+
await DifferentialCore.Instance.Clean(ConfigModel.AppDirectory,
ConfigModel.ReleaseDirectory,
ConfigModel.PatchDirectory);
@@ -165,6 +180,9 @@ await DifferentialCore.Instance.Clean(ConfigModel.AppDirectory,
}
}
+ // Create and save ConfigInfo JSON file
+ var configInfoPath = await CreateConfigInfoFile();
+
var directoryInfo = new DirectoryInfo(ConfigModel.PatchDirectory);
var parentDirectory = directoryInfo.Parent!.FullName;
var operationType = ConfigModel.Format.Value;
@@ -255,4 +273,91 @@ private void CopyDriverFiles(string sourceDir, string targetDir)
CopyDriverFiles(dir, destDir);
}
}
+
+ ///
+ /// Validate required fields
+ ///
+ private async Task ValidateRequiredFields()
+ {
+ var errors = new System.Collections.Generic.List();
+
+ if (string.IsNullOrWhiteSpace(ConfigModel.UpdateUrl))
+ errors.Add("UpdateUrl");
+
+ if (string.IsNullOrWhiteSpace(ConfigModel.ReportUrl))
+ errors.Add("ReportUrl");
+
+ if (string.IsNullOrWhiteSpace(ConfigModel.AppDirectory))
+ errors.Add("AppDirectory");
+
+ if (string.IsNullOrWhiteSpace(ConfigModel.ReleaseDirectory))
+ errors.Add("ReleaseDirectory");
+
+ if (string.IsNullOrWhiteSpace(ConfigModel.PatchDirectory))
+ errors.Add("PatchDirectory");
+
+ if (errors.Any())
+ {
+ var message = $"The following required fields must be filled:\n{string.Join(", ", errors)}";
+ await MessageBox.ShowAsync(message, "Validation Error", Buttons.OK);
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Read project configuration from .csproj file
+ ///
+ private void ReadProjectConfiguration()
+ {
+ try
+ {
+ // Read MainAppName
+ ConfigModel.MainAppName = CsprojReader.ReadMainAppName(ConfigModel.ReleaseDirectory);
+
+ // Read ClientVersion
+ ConfigModel.ClientVersion = CsprojReader.ReadClientVersion(ConfigModel.ReleaseDirectory);
+
+ // Set AppName to MainAppName if MainAppName is not empty
+ if (!string.IsNullOrEmpty(ConfigModel.MainAppName))
+ {
+ ConfigModel.AppName = ConfigModel.MainAppName;
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error reading project configuration: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Create Configinfo JSON file in patch directory
+ ///
+ private async Task CreateConfigInfoFile()
+ {
+ try
+ {
+ var configInfo = new Configinfo
+ {
+ ReportUrl = ConfigModel.ReportUrl,
+ UpdateUrl = ConfigModel.UpdateUrl,
+ AppName = ConfigModel.AppName,
+ MainAppName = ConfigModel.MainAppName,
+ ClientVersion = ConfigModel.ClientVersion
+ };
+
+ var json = JsonConvert.SerializeObject(configInfo, Formatting.Indented);
+ var configFilePath = Path.Combine(ConfigModel.PatchDirectory, "config.json");
+
+ await File.WriteAllTextAsync(configFilePath, json, Encoding.UTF8);
+
+ return configFilePath;
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine($"Error creating config info file: {ex.Message}");
+ throw;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Views/PacketView.axaml b/src/Views/PacketView.axaml
index 76c3c24..8f700d1 100644
--- a/src/Views/PacketView.axaml
+++ b/src/Views/PacketView.axaml
@@ -14,123 +14,221 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+