diff --git a/Source/GrasscutterTools/GrasscutterTools.csproj b/Source/GrasscutterTools/GrasscutterTools.csproj index 4abf15c..85a670f 100644 --- a/Source/GrasscutterTools/GrasscutterTools.csproj +++ b/Source/GrasscutterTools/GrasscutterTools.csproj @@ -282,9 +282,11 @@ + + FormActivityEditor.cs diff --git a/Source/GrasscutterTools/Program.cs b/Source/GrasscutterTools/Program.cs index 3ad8e2d..25b44ed 100644 --- a/Source/GrasscutterTools/Program.cs +++ b/Source/GrasscutterTools/Program.cs @@ -18,11 +18,13 @@ **/ using System; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Forms; - +using GrasscutterTools.OpenCommand; using GrasscutterTools.Properties; using GrasscutterTools.Utils; @@ -63,8 +65,12 @@ namespace GrasscutterTools /// 应用程序的主入口点。 /// [STAThread] - private static void Main() + private static int Main(string[] args) { + var result = HandleCommandLine(args); + if (result != -1) + return result; + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -91,8 +97,129 @@ namespace GrasscutterTools Application.Run(new Forms.FormMain()); Logger.I(TAG, "Program end."); + return 0; } + + #region - 命令行参数 - + + /// + /// 处理命令行参数并返回处理结果 + /// + /// 命令行参数 + /// 返回-1表示继续启动应用程序。返回其它值表示退出应用并将该值作为返回结果。 + private static int HandleCommandLine(string[] args) + { + var parser = new ToggleParser(args); + if (parser.IsEmpty) return -1; + try + { + GuiRedirect.Redirect(); + + // 是否启动日志 + if (parser.HasToggle("debug") || parser.HasToggle("log")) + Logger.IsSaveLogs = true; + + if (parser.HasToggle("v") || parser.HasToggle("version")) + { + Console.WriteLine("v" + Common.AppVersion.ToString(3)); + return 0; + } + + if (parser.HasToggle("h") || parser.HasToggle("help") || parser.HasToggle("?")) + { + Console.WriteLine("Usages:"); + Console.WriteLine(" GcTools.exe -help"); + Console.WriteLine(" GcTools.exe -version"); + Console.WriteLine(" GcTools.exe -c \"cmd arg\""); + Console.WriteLine(" GcTools.exe -c \"cmd1 arg\" && GcTools -c \"cmd2 arg1 arg2\""); + Console.WriteLine(" GcTools.exe -host http://127.0.0.1:443 -token 123456 -c \"cmd1 arg1 arg2 | cmd2 | cmd3 arg\""); + return 0; + } + + // 服务器地址 + var host = parser.GetToggleValueOrDefault("host", Settings.Default.Host); + // 服务器令牌 + var token = parser.GetToggleValueOrDefault("token", Settings.Default.TokenCache); + + if (Settings.Default.Host != host || Settings.Default.TokenCache != token) + { + Settings.Default.Host = host; + Settings.Default.TokenCache = token; + Settings.Default.Save(); + } + +#if DEBUG + Logger.I(TAG, $"Host: {Settings.Default.Host} Token: {Settings.Default.TokenCache}"); +#endif + // UID + //Settings.Default.RemoteUid = decimal.Parse(parser.GetToggleValueOrDefault("uid", Settings.Default.RemoteUid.ToString())); + + + if (!string.IsNullOrEmpty(Settings.Default.Host) && !string.IsNullOrEmpty(Settings.Default.TokenCache)) + { + Common.OC = new OpenCommandAPI(Settings.Default.Host, Settings.Default.TokenCache); + } + + // 解析并执行命令 + var cmd = parser.GetToggleValueOrDefault("c", string.Empty); + if (string.IsNullOrEmpty(cmd)) cmd = parser.GetToggleValueOrDefault("command", string.Empty); + if (!string.IsNullOrEmpty(cmd)) + { + return RunCommand(cmd) ? 0 : 1; + } + } + catch (Exception ex) + { + Logger.E(TAG, "Parse command failed!", ex); + } + return -1; + } + + /// + /// 执行命令 + /// + /// GC命令,由|分割多条命令 + /// 返回是否执行成功 + private static bool RunCommand(string commands) + { + if (Common.OC == null || !Common.OC.CanInvoke) + { + Console.WriteLine(Resources.RequireOpenCommandTip); + Logger.E(TAG, Resources.RequireOpenCommandTip); + return false; + } + + try + { + foreach (var cmd in commands.Split('|').Select(FormatCommand)) + { + var msg = Common.OC.Invoke(cmd).Result; + Console.WriteLine(string.IsNullOrEmpty(msg) ? "OK" : msg); + } + return true; + } + catch (Exception ex) + { + Console.WriteLine(ex); + Logger.E(TAG, "RunCommand Error:", ex); + return false; + } + } + + /// + /// 格式化命令 + /// (去除收尾空白,替换换行) + /// + /// 原始输入 + /// 格式化后可执行命令 + private static string FormatCommand(string raw) + { + return raw.Trim().Replace("\\r", "\r").Replace("\\n", "\n"); + } + + #endregion + #region - 全局异常处理 - private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) diff --git a/Source/GrasscutterTools/Utils/GuiRedirect.cs b/Source/GrasscutterTools/Utils/GuiRedirect.cs new file mode 100644 index 0000000..62e4443 --- /dev/null +++ b/Source/GrasscutterTools/Utils/GuiRedirect.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.InteropServices; + +namespace GrasscutterTools.Utils +{ + /// + /// + /// + public class GuiRedirect + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AttachConsole(int dwProcessId); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr GetStdHandle(StandardHandle nStdHandle); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern FileType GetFileType(IntPtr handle); + //[DllImport("kernel32.dll", SetLastError = true)] + //[return: MarshalAs(UnmanagedType.Bool)] + //static extern bool AllocConsole(); + + private enum StandardHandle : uint + { + Input = unchecked((uint)-10), + Output = unchecked((uint)-11), + Error = unchecked((uint)-12) + } + + private enum FileType : uint + { + Unknown = 0x0000, + Disk = 0x0001, + Char = 0x0002, + Pipe = 0x0003 + } + + private static bool IsRedirected(IntPtr handle) + { + FileType fileType = GetFileType(handle); + + return (fileType == FileType.Disk) || (fileType == FileType.Pipe); + } + + public static void Redirect() + { + if (IsRedirected(GetStdHandle(StandardHandle.Output))) + { + var initialiseOut = Console.Out; + } + + bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error)); + if (errorRedirected) + { + var initialiseError = Console.Error; + } + + AttachConsole(-1); + + if (!errorRedirected) + SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output)); + } + } +} diff --git a/Source/GrasscutterTools/Utils/Logger.cs b/Source/GrasscutterTools/Utils/Logger.cs index 9c73e51..a14d6ca 100644 --- a/Source/GrasscutterTools/Utils/Logger.cs +++ b/Source/GrasscutterTools/Utils/Logger.cs @@ -25,16 +25,17 @@ namespace GrasscutterTools.Utils { public static class Logger { + public static bool IsSaveLogs = false; + private static readonly string LogPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), $"GcTools-{DateTime.Now:MMdd}.log"); private static void Write(string message) { -#if DEBUG - Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}"); -#else - // Test log - //File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}"); -#endif + if (IsSaveLogs) + { + Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}"); + File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}"); + } } private static void Write(string level, string tag, string message) => Write($"<{level}:{tag}> {message}"); diff --git a/Source/GrasscutterTools/Utils/ToggleParser.cs b/Source/GrasscutterTools/Utils/ToggleParser.cs new file mode 100644 index 0000000..e537e22 --- /dev/null +++ b/Source/GrasscutterTools/Utils/ToggleParser.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GrasscutterTools.Utils +{ + /// + /// Simple command line toggles parser: + /// - toggles are identified with (any number of) '-' prefixes + /// - toggle can be with or without associated value + /// - toggles are case-insensitive + /// + /// --toggle_without_value -toggle value + /// + /// + internal class ToggleParser + { + private readonly Dictionary toggles; + + public ToggleParser(string[] args) + { + toggles = + args.Zip(args.Skip(1).Concat(new[] { string.Empty }), (first, second) => new { first, second }) + .Where(pair => IsToggle(pair.first)) + .ToDictionary(pair => RemovePrefix(pair.first).ToLowerInvariant(), g => IsToggle(g.second) ? string.Empty : g.second); + } + + private static string RemovePrefix(string toggle) + => new string(toggle.SkipWhile(c => c == '-').ToArray()); + + private static bool IsToggle(string arg) + => arg.StartsWith("-", StringComparison.InvariantCulture); + + public bool HasToggle(string toggle) + => toggles.ContainsKey(toggle.ToLowerInvariant()); + + public string GetToggleValueOrDefault(string toggle, string defaultValue) + => toggles.TryGetValue(toggle.ToLowerInvariant(), out var value) ? value : defaultValue; + + public bool IsEmpty => toggles.Count == 0; + } +}