diff --git a/src/Export.cs b/src/Export.cs index 6e11e26..89642b7 100644 --- a/src/Export.cs +++ b/src/Export.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Text; +using Microsoft.Win32; +using Newtonsoft.Json; using static AchievementAllDataNotify.Types.Achievement.Types; namespace YaeAchievement; @@ -7,36 +11,55 @@ public static class Export { public static void Choose(AchievementAllDataNotify data) { Console.Write(@"导出至: - [0] 椰羊 (https://cocogoat.work/achievement) + [0] 椰羊 (https://cocogoat.work/achievement, 默认) [1] SnapGenshin [2] Paimon.moe [3] Seelie.me - [4] 表格文件 (默认) + [4] 表格文件 输入一个数字(0-4): ".Split("\n").Select(s => s.Trim()).JoinToString("\n") + " "); - if (!int.TryParse(Console.ReadLine(), out var num)) num = 4; + if (!int.TryParse(Console.ReadLine(), out var num)) num = 0; Action act = num switch { - 0 => ToCocogoat, 1 => ToSnapGenshin, 2 => ToPaimon, 3 => ToSeelie, + 4 => ToCSV, 7 => ToRawJson, - _ => ToCSV + _ => ToCocogoat }; act(data); } private static void ToCocogoat(AchievementAllDataNotify data) { - throw new NotImplementedException(); + var result = JsonConvert.SerializeObject(ExportToUIAFApp(data)); + using var request = new HttpRequestMessage { + Method = HttpMethod.Post, + RequestUri = new Uri("https://77.cocogoat.work/v1/memo?source=全部成就"), + Content = new StringContent(result, Encoding.UTF8, "application/json") + }; + using var response = Utils.CHttpClient.Value.Send(request); + if (response.StatusCode != HttpStatusCode.Created) { + Console.WriteLine("导出失败, 请联系开发者以获取帮助"); + return; + } + dynamic memo = JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result)!; + Console.WriteLine(Utils.ShellOpen($"https://cocogoat.work/achievement?memo={memo.key}") + ? "在浏览器内进行下一步操作" + : $"https://cocogoat.work/achievement?memo={memo.key}"); } private static void ToSnapGenshin(AchievementAllDataNotify data) { - throw new NotImplementedException(); + if (CheckSnapScheme()) { + Utils.CopyToClipboard(JsonConvert.SerializeObject(ExportToUIAFApp(data))); + Utils.ShellOpen("snapgenshin://achievement/import/uiaf"); + Console.WriteLine("在 SnapGenshin 进行下一步操作"); + } else { + Console.WriteLine("更新 SnapGenshin 至最新版本后重试"); + } } private static void ToPaimon(AchievementAllDataNotify data) { var info = LoadAchievementInfo(); var output = new Dictionary>(); - var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json"); foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) { if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) { Console.WriteLine($"Unable to find {ach.Id} in metadata."); @@ -49,19 +72,29 @@ public static class Export { var final = new Dictionary>> { ["achievement"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value) }; + var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json"); File.WriteAllText(path, JsonConvert.SerializeObject(final)); Console.WriteLine($"成就数据已导出至 {path}"); } private static void ToSeelie(AchievementAllDataNotify data) { + var output = new Dictionary>(); + foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) { + output[ach.Id == 81222 ? 81219 : ach.Id] = new Dictionary { + ["done"] = true + }; + } + var final = new Dictionary>> { + ["achievements"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value) + }; var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json"); + File.WriteAllText(path, JsonConvert.SerializeObject(final)); Console.WriteLine($"成就数据已导出至 {path}"); } // ReSharper disable once InconsistentNaming private static void ToCSV(AchievementAllDataNotify data) { var info = LoadAchievementInfo(); - var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv"); var outList = new List>(); foreach (var ach in data.List.OrderBy(a => a.Id)) { if (UnusedAchievement.Contains(ach.Id)) continue; @@ -85,6 +118,7 @@ public static class Export { item[2] = info.Group[(uint) item[2]]; return item.JoinToString(","); })); + var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv"); File.WriteAllText(path, $"\uFEFF{string.Join("\n", output)}"); Console.WriteLine($"成就数据已导出至 {path}"); } @@ -96,8 +130,25 @@ public static class Export { } // ReSharper disable once InconsistentNaming - private static string ExportToUIAFApp(AchievementAllDataNotify data) { - return ""; + private static Dictionary ExportToUIAFApp(AchievementAllDataNotify data) { + var output = data.List + .Where(a => a.Status is Status.Finished or Status.RewardTaken) + .Select(ach => new Dictionary { ["id"] = ach.Id, ["current"] = ach.Current, ["timestamp"] = ach.Timestamp }) + .ToList(); + return new Dictionary { + ["info"] = new Dictionary { + ["export_app"] = "YaeAchievement", + ["export_timestamp"] = DateTimeOffset.Now.ToUnixTimeMilliseconds(), + ["export_app_version"] = GlobalVars.AppVersionName, + ["uiaf_version"] = "v1.0" + }, + ["list"] = output + }; + } + + [SuppressMessage("Interoperability", "CA1416:验证平台兼容性")] + private static bool CheckSnapScheme() { + return (string?)Registry.ClassesRoot.OpenSubKey("snapgenshin")?.GetValue("") == "URL:snapgenshin"; } private static string JoinToString(this IEnumerable list, string separator) { diff --git a/src/Program.cs b/src/Program.cs index 3756535..5407ec3 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -5,18 +5,19 @@ using YaeAchievement; using static YaeAchievement.Utils; Console.WriteLine("----------------------------------------------------"); -Console.WriteLine("YaeAchievement - 原神成就导出工具"); +Console.WriteLine($"YaeAchievement - 原神成就导出工具 ({GlobalVars.AppVersionName})"); Console.WriteLine("https://github.com/HolographicHat/YaeAchievement"); Console.WriteLine("----------------------------------------------------"); -/*InstallExitHook(); +InstallExitHook(); InstallExceptionHook(); CheckGenshinIsRunning(); LoadConfig(); -CheckUpdate();*/ +CheckUpdate(); TryDisableQuickEdit(); //Console.WriteLine(c.Send(msg).StatusCode); //using var o = File.OpenWrite("ai"); //AchievementInfo.Parser.ParseJson(File.ReadAllText(@"C:\Users\holog\Desktop\cc.json")).WriteTo(o); +//CopyToClipboard("test"); StartAndWaitResult(@"D:\Genshin Impact Dev\2.8\YuanShen.exe", str => { GlobalVars.UnexpectedExit = false; var list = AchievementAllDataNotify.Parser.ParseFrom(Convert.FromBase64String(str)); diff --git a/src/Utils.cs b/src/Utils.cs index 9784f0d..6d21ef6 100644 --- a/src/Utils.cs +++ b/src/Utils.cs @@ -16,7 +16,7 @@ namespace YaeAchievement; public static class Utils { - private static readonly Lazy CHttpClient = new (() => { + public static readonly Lazy CHttpClient = new (() => { var c = new HttpClient(new HttpClientHandler { Proxy = new WebProxy("http://127.0.0.1:8888"), AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip @@ -81,6 +81,21 @@ public static class Utils { var b = md5.ComputeHash(bytes); return Convert.ToHexString(b).ToLower(); } + + public static void CopyToClipboard(string text) { + if (Native.OpenClipboard(IntPtr.Zero)) { + Native.EmptyClipboard(); + var hGlobal = Marshal.AllocHGlobal((text.Length + 1) * 2); + var hPtr = Native.GlobalLock(hGlobal); + Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length); + Native.GlobalUnlock(hPtr); + Native.SetClipboardData(13, hGlobal); + Marshal.FreeHGlobal(hGlobal); + Native.CloseClipboard(); + } else { + throw new Win32Exception(); + } + } public static void LoadConfig() { var conf = JsonNode.Parse(File.ReadAllText(GlobalVars.ConfigFileName))!; @@ -104,12 +119,7 @@ public static class Utils { var fullPath = Path.GetFullPath("update.7z"); File.WriteAllBytes(fullPath, GetBucketFileAsByteArray(info.PackageLink)); Console.WriteLine("下载完毕! 关闭程序后, 将压缩包解压至当前目录即可完成更新."); - new Process { - StartInfo = { - FileName = fullPath, - UseShellExecute = true - } - }.Start(); + ShellOpen(fullPath); Environment.Exit(0); } Console.WriteLine($"下载地址: {info.PackageLink}"); @@ -123,6 +133,15 @@ public static class Utils { } } + public static bool ShellOpen(string path) { + return new Process { + StartInfo = { + FileName = path, + UseShellExecute = true + } + }.Start(); + } + private static bool CheckGamePathValid(string path) { var dir = Path.GetDirectoryName(path)!; return File.Exists($"{dir}/UnityPlayer.dll") && File.Exists($"{dir}/mhypbase.dll"); diff --git a/src/Win32/Native.cs b/src/Win32/Native.cs index 51ac2bb..45525cc 100644 --- a/src/Win32/Native.cs +++ b/src/Win32/Native.cs @@ -93,4 +93,24 @@ public static class Native { [DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool GetOpenFileName([In, Out] OpenFileName ofn); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GlobalLock(IntPtr mem); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GlobalUnlock(IntPtr mem); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenClipboard(IntPtr owner); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll", SetLastError = true)] + public static extern bool CloseClipboard(); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetClipboardData(uint uFormat, IntPtr data); + + [DllImport("user32.dll")] + public static extern bool EmptyClipboard(); }