spectre console

This commit is contained in:
HolographicHat
2025-04-11 23:57:07 +08:00
parent 0e7be25b23
commit 980a47bf43
11 changed files with 314 additions and 142 deletions

View File

@@ -21,15 +21,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Google.Protobuf" Version="3.29.0"/>
<PackageReference Include="Grpc.Tools" Version="2.67.0">
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Spectre.Console" Version="0.49.1"/>
<PackageReference Include="Spectre.Console" Version="0.50.1-preview.0.3" />
<PackageReference Include="Spectre.Console.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -55,6 +59,7 @@
<PropertyGroup>
<CETCompat>false</CETCompat>
<!-- <TrimmerSingleWarn>false</TrimmerSingleWarn>-->
</PropertyGroup>
</Project>

View File

@@ -86,6 +86,24 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
internal static string CommonNo {
get {
return ResourceManager.GetString("CommonNo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
internal static string CommonYes {
get {
return ResourceManager.GetString("CommonYes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You need to login genshin impact before exporting..
/// </summary>
@@ -114,16 +132,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Export to:
///[0] Cocogoat (https://cocogoat.work/achievement, Default)
///[1] Snap.HuTao
///[2] Paimon.moe
///[3] Seelie.me
///[4] Csv file
///[5] Xunkong
///[7] Teyvat Guide
///[8] UIAF JSON File
///Input a number (0-8): .
/// Looks up a localized string similar to Export to:.
/// </summary>
internal static string ExportChoose {
get {
@@ -131,6 +140,87 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Cocogoat (https://cocogoat.work/achievement).
/// </summary>
internal static string ExportTargetCocogoat {
get {
return ResourceManager.GetString("ExportTargetCocogoat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Csv file.
/// </summary>
internal static string ExportTargetCsv {
get {
return ResourceManager.GetString("ExportTargetCsv", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Snap.HuTao.
/// </summary>
internal static string ExportTargetHuTao {
get {
return ResourceManager.GetString("ExportTargetHuTao", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Paimon.moe.
/// </summary>
internal static string ExportTargetPaimon {
get {
return ResourceManager.GetString("ExportTargetPaimon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seelie.me.
/// </summary>
internal static string ExportTargetSeelie {
get {
return ResourceManager.GetString("ExportTargetSeelie", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Teyvat Guide.
/// </summary>
internal static string ExportTargetTeyvatGuide {
get {
return ResourceManager.GetString("ExportTargetTeyvatGuide", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to UIAF JSON File.
/// </summary>
internal static string ExportTargetUIAFJson {
get {
return ResourceManager.GetString("ExportTargetUIAFJson", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string ExportTargetWxApp1 {
get {
return ResourceManager.GetString("ExportTargetWxApp1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Xunkong.
/// </summary>
internal static string ExportTargetXunkong {
get {
return ResourceManager.GetString("ExportTargetXunkong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fail, please contact developer to get help information.
/// </summary>
@@ -320,6 +410,15 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Checking update....
/// </summary>
internal static string UpdateChecking {
get {
return ResourceManager.GetString("UpdateChecking", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Description:
///{0}.
@@ -358,7 +457,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Use previous fetched data? (yes|no).
/// Looks up a localized string similar to Use previous fetched data?.
/// </summary>
internal static string UsePreviousData {
get {

View File

@@ -25,16 +25,7 @@
<value>all achievement</value>
</data>
<data name="ExportChoose" xml:space="preserve">
<value>Export to:
[0] Cocogoat (https://cocogoat.work/achievement, Default)
[1] Snap.HuTao
[2] Paimon.moe
[3] Seelie.me
[4] Csv file
[5] Xunkong
[7] Teyvat Guide
[8] UIAF JSON File
Input a number (0-8): </value>
<value>Export to:</value>
</data>
<data name="ExportToCocogoatSuccess" xml:space="preserve">
<value>Successfully exported to cocogoat.</value>
@@ -107,7 +98,7 @@ Input a number (0-8): </value>
<value>YaeAchievement ({0})</value>
</data>
<data name="UsePreviousData" xml:space="preserve">
<value>Use previous fetched data? (yes|no)</value>
<value>Use previous fetched data?</value>
</data>
<data name="NetworkError" xml:space="preserve">
<value>Network error:</value>
@@ -121,7 +112,6 @@ Input a number (0-8): </value>
<data name="ExceptionNetwork" xml:space="preserve">
<value>Network error ({0}: {1})</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="GenshinHashError" xml:space="preserve">
<value>Please update genshin and retry.</value>
</data>
@@ -137,4 +127,40 @@ Input a number (0-8): </value>
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>Please update game and retry.</value>
</data>
<data name="UpdateChecking" xml:space="preserve">
<value>Checking update...</value>
</data>
<data name="CommonYes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="CommonNo" xml:space="preserve">
<value>No</value>
</data>
<data name="ExportTargetCocogoat" xml:space="preserve">
<value>Cocogoat (https://cocogoat.work/achievement)</value>
</data>
<data name="ExportTargetHuTao" xml:space="preserve">
<value>Snap.HuTao</value>
</data>
<data name="ExportTargetPaimon" xml:space="preserve">
<value>Paimon.moe</value>
</data>
<data name="ExportTargetSeelie" xml:space="preserve">
<value>Seelie.me</value>
</data>
<data name="ExportTargetCsv" xml:space="preserve">
<value>Csv file</value>
</data>
<data name="ExportTargetXunkong" xml:space="preserve">
<value>Xunkong</value>
</data>
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
<value>Teyvat Guide</value>
</data>
<data name="ExportTargetUIAFJson" xml:space="preserve">
<value>UIAF JSON File</value>
</data>
<data name="ExportTargetWxApp1" xml:space="preserve">
<value />
</data>
</root>

View File

@@ -18,17 +18,7 @@
<value>全部成就</value>
</data>
<data name="ExportChoose" xml:space="preserve">
<value>导出至:
[0] 椰羊 (https://cocogoat.work/achievement, 默认)
[1] Snap Hutao
[2] Paimon.moe
[3] Seelie.me
[4] 表格文件
[5] 寻空
[6] 原魔工具箱
[7] Teyvat Guide
[8] UIAF JSON 文件
输入一个数字 (0-8): </value>
<value>导出至:</value>
</data>
<data name="ExportToCocogoatSuccess" xml:space="preserve">
<value>在浏览器内进行下一步操作</value>
@@ -101,7 +91,7 @@
<value>YaeAchievement - 原神成就导出工具 ({0})</value>
</data>
<data name="UsePreviousData" xml:space="preserve">
<value>要使用上一次获取到的成就数据吗? (yes|no)</value>
<value>要使用上一次获取到的成就数据吗? </value>
</data>
<data name="NetworkError" xml:space="preserve">
<value>网络错误: {0}</value>
@@ -130,4 +120,40 @@
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>当前元数据版本不匹配,请更新原神至最新版本或等待元数据更新后重试。</value>
</data>
<data name="UpdateChecking" xml:space="preserve">
<value>正在检查更新...</value>
</data>
<data name="CommonYes" xml:space="preserve">
<value>是</value>
</data>
<data name="CommonNo" xml:space="preserve">
<value>否</value>
</data>
<data name="ExportTargetCocogoat" xml:space="preserve">
<value>椰羊 (https://cocogoat.work/achievement)</value>
</data>
<data name="ExportTargetHuTao" xml:space="preserve">
<value>Snap Hutao</value>
</data>
<data name="ExportTargetPaimon" xml:space="preserve">
<value>Paimon.moe</value>
</data>
<data name="ExportTargetSeelie" xml:space="preserve">
<value>Seelie.me</value>
</data>
<data name="ExportTargetCsv" xml:space="preserve">
<value>表格文件</value>
</data>
<data name="ExportTargetXunkong" xml:space="preserve">
<value>寻空</value>
</data>
<data name="ExportTargetUIAFJson" xml:space="preserve">
<value>UIAF JSON 文件</value>
</data>
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
<value>Teyvat Guide</value>
</data>
<data name="ExportTargetWxApp1" xml:space="preserve">
<value>原魔工具箱</value>
</data>
</root>

View File

@@ -5,37 +5,40 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Win32;
using Spectre.Console;
using YaeAchievement.Outputs;
using YaeAchievement.Parsers;
using YaeAchievement.res;
// ReSharper disable UnusedMember.Local
namespace YaeAchievement;
public static class Export {
public static uint ExportTo { get; set; } = uint.MaxValue;
public static int ExportTo { get; set; } = 114514;
public static void Choose(AchievementAllDataNotify data) {
if (ExportTo == uint.MaxValue) {
Console.Write(App.ExportChoose);
while (Console.KeyAvailable) {
Console.ReadKey(false);
}
if (!uint.TryParse(Console.ReadLine(), out var num)) num = 0;
ExportTo = num;
var targets = new Dictionary<string, Action<AchievementAllDataNotify>> {
{ App.ExportTargetCocogoat, ToCocogoat },
{ App.ExportTargetHuTao, ToHuTao },
{ App.ExportTargetPaimon, ToPaimon },
{ App.ExportTargetSeelie, ToSeelie },
{ App.ExportTargetCsv, ToCSV },
{ App.ExportTargetXunkong, ToXunkong },
// { App.ExportTargetWxApp1, ToWxApp1 },
{ App.ExportTargetTeyvatGuide, ToTeyvatGuide },
{ App.ExportTargetUIAFJson, ToUIAFJson },
// { "", ToRawJson }
};
Action<AchievementAllDataNotify> action;
if (ExportTo == 114514) {
var prompt = new SelectionPrompt<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
action = targets[AnsiConsole.Prompt(prompt)];
} else {
action = targets.ElementAtOrDefault(ExportTo).Value ?? ToCocogoat;
}
((Action<AchievementAllDataNotify>) (ExportTo switch {
1 => ToHuTao,
2 => ToPaimon,
3 => ToSeelie,
4 => ToCSV,
5 => ToXunkong,
6 => ToWxApp1,
7 => ToTeyvatGuide,
8 => ToUIAFJson,
9 => ToRawJson,
_ => ToCocogoat
})).Invoke(data);
action(data);
}
private static void ToCocogoat(AchievementAllDataNotify data) {
@@ -46,17 +49,17 @@ public static class Export {
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
using var response = Utils.CHttpClient.Send(request);
if (response.StatusCode != HttpStatusCode.Created) {
Console.WriteLine(App.ExportToCocogoatFail);
AnsiConsole.WriteLine(App.ExportToCocogoatFail);
return;
}
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var responseJson = JsonSerializer.Deserialize(responseText, CocogoatResponseContext.Default.CocogoatResponse)!;
var cocogoatUrl = $"https://cocogoat.work/achievement?memo={responseJson.Key}";
Utils.SetQuickEditMode(true);
Console.WriteLine(cocogoatUrl);
AnsiConsole.MarkupLineInterpolated($"[link]{cocogoatUrl}[/]");
if (Utils.ShellOpen(cocogoatUrl))
{
Console.WriteLine(App.ExportToCocogoatSuccess);
AnsiConsole.WriteLine(App.ExportToCocogoatSuccess);
}
}
@@ -68,16 +71,16 @@ public static class Export {
request.RequestUri = new Uri("https://api.qyinter.com/achievementRedis");
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
using var response = Utils.CHttpClient.Send(request);
Console.WriteLine(App.ExportToWxApp1Success, id);
AnsiConsole.WriteLine(App.ExportToWxApp1Success, id);
}
private static void ToHuTao(AchievementAllDataNotify data) {
if (CheckWinUIAppScheme("hutao")) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("hutao://achievement/import");
Console.WriteLine(App.ExportToSnapGenshinSuccess);
AnsiConsole.WriteLine(App.ExportToSnapGenshinSuccess);
} else {
Console.WriteLine(App.ExportToSnapGenshinNeedUpdate);
AnsiConsole.WriteLine(App.ExportToSnapGenshinNeedUpdate);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9PH4NXJ2JN52");
}
}
@@ -86,9 +89,9 @@ public static class Export {
if (CheckWinUIAppScheme("xunkong")) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
Console.WriteLine(App.ExportToXunkongSuccess);
AnsiConsole.WriteLine(App.ExportToXunkongSuccess);
} else {
Console.WriteLine(App.ExportToXunkongNeedUpdate);
AnsiConsole.WriteLine(App.ExportToXunkongNeedUpdate);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
}
}
@@ -97,9 +100,9 @@ public static class Export {
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("teyvatguide://import_uiaf?app=Yae");
Console.WriteLine(App.ExportToTauriSuccess);
AnsiConsole.WriteLine(App.ExportToTauriSuccess);
} else {
Console.WriteLine(App.ExportToTauriFail);
AnsiConsole.WriteLine(App.ExportToTauriFail);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9NLBNNNBNSJN");
}
}
@@ -108,21 +111,21 @@ public static class Export {
private static void ToUIAFJson(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"uiaf-{DateTime.Now:yyyyMMddHHmmss}.json");
if (TryWriteToFile(path, UIAFSerializer.Serialize(data))) {
Console.WriteLine(App.ExportToFileSuccess, path);
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
private static void ToPaimon(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
if (TryWriteToFile(path, PaimonSerializer.Serialize(data))) {
Console.WriteLine(App.ExportToFileSuccess, path);
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
private static void ToSeelie(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
if (TryWriteToFile(path, SeelieSerializer.Serialize(data))) {
Console.WriteLine(App.ExportToFileSuccess, path);
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
@@ -133,7 +136,7 @@ public static class Export {
foreach (var ach in data.AchievementList.OrderBy(a => a.Id)) {
if (UnusedAchievement.Contains(ach.Id)) continue;
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
AnsiConsole.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var finishAt = "";
@@ -156,7 +159,7 @@ public static class Export {
}));
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
if (TryWriteToFile(path, $"\uFEFF{string.Join("\n", output)}")) {
Console.WriteLine(App.ExportToFileSuccess, path);
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
Process.Start("explorer.exe", $"{Path.GetDirectoryName(path)}");
}
}
@@ -165,7 +168,7 @@ public static class Export {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
var text = AchievementRawDataSerializer.Serialize(data);
if (TryWriteToFile(path, text)) {
Console.WriteLine(App.ExportToFileSuccess, path);
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
@@ -192,7 +195,7 @@ public static class Export {
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
// ReSharper disable once LocalizableElement
Console.WriteLine($"{msg}: {ex.Message}");
AnsiConsole.WriteLine($"{msg}: {ex.Message}");
return ex.NativeErrorCode;
}
@@ -201,7 +204,7 @@ public static class Export {
File.WriteAllText(path, contents);
return true;
} catch (UnauthorizedAccessException) {
Console.WriteLine(App.NoWritePermission, path);
AnsiConsole.WriteLine(App.NoWritePermission, path);
return false;
}
}

View File

@@ -4,11 +4,6 @@ using Proto;
namespace YaeAchievement;
// ReSharper disable InconsistentNaming
// ReSharper disable ConvertToConstant.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable once MemberCanBePrivate.Global
public static class GlobalVars {
public static bool PauseOnExit { get; set; } = true;
@@ -24,8 +19,6 @@ public static class GlobalVars {
public const string AppVersionName = "5.3";
public const string PipeName = "YaeAchievementPipe";
public const string RinBucketHost = "https://rin.holohat.work";
public const string SakuraBucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
[field:MaybeNull]
public static AchievementInfo AchievementInfo =>

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Spectre.Console;
using YaeAchievement.res;
using YaeAchievement.Utilities;
@@ -73,7 +74,7 @@ public class AchievementAllDataNotify {
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
Console.WriteLine("Parse failed");
AnsiConsole.WriteLine("Parse failed");
File.WriteAllBytes("achievement_raw_data.bin", bytes);
Environment.Exit(0);
}
@@ -98,7 +99,7 @@ public class AchievementAllDataNotify {
.FieldIds;
#if DEBUG
// ReSharper disable once LocalizableElement
Console.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
AnsiConsole.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
#endif
} else {
var info = GlobalVars.AchievementInfo.PbInfo; // ...
@@ -108,7 +109,7 @@ public class AchievementAllDataNotify {
totalId = info.TotalProgress;
currentId = info.CurrentProgress;
if (data.Any(dict => !dict.ContainsKey(iId) || !dict.ContainsKey(sId) || !dict.ContainsKey(totalId))) {
Console.WriteLine(App.WaitMetadataUpdate);
AnsiConsole.WriteLine(App.WaitMetadataUpdate);
Environment.Exit(0);
}
}

View File

@@ -1,5 +1,6 @@
using Google.Protobuf;
using Proto;
using Spectre.Console;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable CollectionNeverQueried.Global
@@ -51,7 +52,7 @@ public class PlayerStoreNotify {
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
Console.WriteLine("Parse failed");
AnsiConsole.WriteLine("Parse failed");
File.WriteAllBytes("store_raw_data.bin", bytes);
Environment.Exit(0);
}

View File

@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using System.Text;
using Spectre.Console;
using YaeAchievement.Parsers;
using YaeAchievement.res;
using YaeAchievement.Utilities;
@@ -11,8 +12,13 @@ internal static class Program {
public static async Task Main(string[] args) {
AnsiConsole.WriteLine(@"----------------------------------------------------");
AnsiConsole.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
AnsiConsole.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
AnsiConsole.WriteLine(@"----------------------------------------------------");
if (!new Mutex(true, @"Global\YaeMiku~uwu").WaitOne(0, false)) {
Console.WriteLine(App.AnotherInstance);
AnsiConsole.WriteLine(App.AnotherInstance);
Environment.Exit(302);
}
@@ -21,15 +27,10 @@ internal static class Program {
CheckGenshinIsRunning();
Console.WriteLine(@"----------------------------------------------------");
Console.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
Console.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
Console.WriteLine(@"----------------------------------------------------");
AppConfig.Load(args.GetOrNull(0) ?? "auto");
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
await CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
AchievementAllDataNotify? data = null;
try {
@@ -39,8 +40,10 @@ internal static class Program {
} catch (Exception) { /* ignored */ }
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
Console.WriteLine(App.UsePreviousData);
if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
var prompt = new SelectionPrompt<string>()
.Title(App.UsePreviousData)
.AddChoices(App.CommonYes, App.CommonNo);
if (AnsiConsole.Prompt(prompt) == App.CommonYes) {
Export.Choose(data);
return;
}
@@ -51,7 +54,7 @@ internal static class Program {
{ 2, PlayerStoreNotify.OnReceive },
{ 100, PlayerPropNotify.OnReceive },
}, () => {
#if DEBUG
#if DEBUG_EX
PlayerPropNotify.OnFinish();
File.WriteAllText("store_data.json", JsonSerializer.Serialize(PlayerStoreNotify.Instance, new JsonSerializerOptions {
WriteIndented = true,

View File

@@ -9,6 +9,7 @@ using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Proto;
using Spectre.Console;
using YaeAchievement.res;
using YaeAchievement.Utilities;
@@ -28,31 +29,36 @@ public static class Utils {
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
try {
return await await Task.WhenAny(GetFile(GlobalVars.RinBucketHost), GetFile(GlobalVars.SakuraBucketHost));
} catch (Exception e) when(e is SocketException or TaskCanceledException) {
Console.WriteLine(App.NetworkError, e.Message);
Environment.Exit(-1);
return null!;
return await GetFile("https://api.qhy04.com/hutaocdn/download?filename={0}", path, useCache);
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
}
async Task<byte[]> GetFile(string host) {
using var msg = new HttpRequestMessage();
msg.Method = HttpMethod.Get;
msg.RequestUri = new Uri($"{host}/{path}");
try {
return await Task.WhenAny(
GetFile("https://rin.holohat.work/{0}", path, useCache),
GetFile("https://cn-cd-1259389942.file.myqcloud.com/{0}", path, useCache)
).Unwrap();
} catch (Exception ex) when (ex is SocketException or TaskCanceledException) {
AnsiConsole.WriteLine(App.NetworkError, ex.Message);
Environment.Exit(-1);
}
return null!;
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) {
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey));
CacheItem? cache = null;
if (useCache && CacheFile.TryRead(path, out cache)) {
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
if (useCache && CacheFile.TryRead(objectKey, out cache)) {
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
}
using var response = await CHttpClient.SendAsync(msg);
using var response = await CHttpClient.SendAsync(reqwest);
if (cache != null && response.StatusCode == HttpStatusCode.NotModified) {
return cache.Content.ToByteArray();
}
response.EnsureSuccessStatusCode();
var responseBytes = await response.Content.ReadAsByteArrayAsync();
var bytes = await response.Content.ReadAsByteArrayAsync();
if (useCache) {
var etag = response.Headers.ETag!.Tag;
CacheFile.Write(path, responseBytes, etag);
CacheFile.Write(objectKey, bytes, etag);
}
return responseBytes;
return bytes;
}
}
@@ -60,12 +66,12 @@ public static class Utils {
return array.Length > index ? array[index] : null;
}
public static uint? ToUIntOrNull(string? value) {
return value != null ? uint.TryParse(value, out var result) ? result : null : null;
public static int ToIntOrDefault(string? value, int defaultValue = 0) {
return value != null && int.TryParse(value, out var result) ? result : defaultValue;
}
public static bool ToBooleanOrFalse(string? value) {
return value != null && bool.TryParse(value, out var result) && result;
public static bool ToBooleanOrDefault(string? value, bool defaultValue = false) {
return value != null && bool.TryParse(value, out var result) ? result : defaultValue;
}
public static unsafe void CopyToClipboard(string text) {
@@ -86,35 +92,44 @@ public static class Utils {
// ReSharper disable once NotAccessedField.Local
private static UpdateInfo _updateInfo = null!;
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
}
public static Task<T> StartSpinnerAsync<T>(string status, Func<StatusContext, Task<T>> func) {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
}
public static async Task CheckUpdate(bool useLocalLib) {
var info = UpdateInfo.Parser.ParseFrom(await GetBucketFile("schicksal/version"))!;
if (GlobalVars.AppVersionCode < info.VersionCode) {
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
Console.WriteLine(App.UpdateDescription, info.Description);
if (info.EnableAutoUpdate) {
Console.WriteLine(App.UpdateDownloading);
var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version"));
var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!;
if (GlobalVars.AppVersionCode < versionInfo.VersionCode) {
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
if (versionInfo.EnableAutoUpdate) {
var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink));
var tmpPath = Path.GetTempFileName();
await File.WriteAllBytesAsync(tmpPath, await GetBucketFile(info.PackageLink));
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) {
await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!;
await srcStream.CopyToAsync(dstStream);
}
await File.WriteAllBytesAsync(tmpPath, newBin);
ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\"");
await Task.Delay(1919810);
await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810));
GlobalVars.PauseOnExit = false;
Environment.Exit(0);
}
Console.WriteLine(App.DownloadLink, info.PackageLink);
if (info.ForceUpdate) {
AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink);
if (versionInfo.ForceUpdate) {
Environment.Exit(0);
}
}
if (info.EnableLibDownload && !useLocalLib) {
if (versionInfo.EnableLibDownload && !useLocalLib) {
var data = await GetBucketFile("schicksal/lic.dll");
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data);
}
_updateInfo = info;
_updateInfo = versionInfo;
}
// ReSharper disable once UnusedMethodReturnValue.Global
@@ -142,7 +157,7 @@ public static class Utils {
try {
using var handle = File.OpenHandle($"{path}/output_log.txt", share: FileShare.None);
} catch (IOException) {
Console.WriteLine(App.GenshinIsRunning, 0);
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
Environment.Exit(301);
}
}
@@ -154,7 +169,7 @@ public static class Utils {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
_proc?.Terminate(0);
if (GlobalVars.PauseOnExit) {
Console.WriteLine(App.PressKeyToExit);
AnsiConsole.WriteLine(App.PressKeyToExit);
Console.ReadKey();
}
};
@@ -165,16 +180,16 @@ public static class Utils {
var ex = e.ExceptionObject;
switch (ex) {
case ApplicationException ex1:
Console.WriteLine(ex1.Message);
AnsiConsole.WriteLine(ex1.Message);
break;
case SocketException ex2:
Console.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
break;
case HttpRequestException ex3:
Console.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
break;
default:
Console.WriteLine(ex.ToString());
AnsiConsole.WriteLine(ex.ToString()!);
break;
}
Environment.Exit(-1);
@@ -189,13 +204,13 @@ public static class Utils {
_proc.OnExit += () => {
if (_isUnexpectedExit) {
_proc = null;
Console.WriteLine(App.GameProcessExit);
AnsiConsole.WriteLine(App.GameProcessExit);
Environment.Exit(114514);
}
};
_proc.LoadLibrary(GlobalVars.LibFilePath);
_proc.ResumeMainThread();
Console.WriteLine(App.GameLoading, _proc.Id);
AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
Task.Run(() => {
using var stream = new NamedPipeServerStream(GlobalVars.PipeName);
using var reader = new BinaryReader(stream);