This commit is contained in:
HolographicHat
2025-06-07 20:35:24 +08:00
parent 8f9a26a237
commit 645fe38c65
14 changed files with 155 additions and 26 deletions

View File

@@ -18,4 +18,6 @@ TerminateProcess
VirtualAllocEx
VirtualFreeEx
WaitForSingleObject
WriteProcessMemory
WriteProcessMemory
GetCurrentConsoleFontEx

View File

@@ -374,6 +374,42 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Use the keyboard arrow keys to move the cursor and the Enter key to select.
/// </summary>
internal static string SelectionPromptCompatAnsiTip {
get {
return ResourceManager.GetString("SelectionPromptCompatAnsiTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose an option:.
/// </summary>
internal static string SelectionPromptCompatChooseOne {
get {
return ResourceManager.GetString("SelectionPromptCompatChooseOne", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please enter a number between 0 and {0}.
/// </summary>
internal static string SelectionPromptCompatInvalidChoice {
get {
return ResourceManager.GetString("SelectionPromptCompatInvalidChoice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type a number and press Enter to select.
/// </summary>
internal static string SelectionPromptCompatNonAnsiTip {
get {
return ResourceManager.GetString("SelectionPromptCompatNonAnsiTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reward not taken.
/// </summary>

View File

@@ -163,4 +163,16 @@
<data name="ExportTargetWxApp1" xml:space="preserve">
<value />
</data>
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
<value>Choose an option:</value>
</data>
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
<value>Use the keyboard arrow keys to move the cursor and the Enter key to select</value>
</data>
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
<value>Type a number and press Enter to select</value>
</data>
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
<value>Please enter a number between 0 and {0}</value>
</data>
</root>

View File

@@ -18,7 +18,7 @@
<value>全部成就</value>
</data>
<data name="ExportChoose" xml:space="preserve">
<value>要导出到哪里?(键盘上下键移动光标,键盘回车键选择)</value>
<value>要导出到哪里?</value>
</data>
<data name="ExportToCocogoatSuccess" xml:space="preserve">
<value>在浏览器内进行下一步操作</value>
@@ -91,7 +91,7 @@
<value>YaeAchievement - 原神成就导出工具 ({0})</value>
</data>
<data name="UsePreviousData" xml:space="preserve">
<value>要使用上一次获取到的成就数据吗?(键盘上下键移动光标,键盘回车键选择)</value>
<value>要使用上一次获取到的成就数据吗?</value>
</data>
<data name="NetworkError" xml:space="preserve">
<value>网络错误: {0}</value>
@@ -156,4 +156,16 @@
<data name="ExportTargetWxApp1" xml:space="preserve">
<value>原魔工具箱</value>
</data>
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
<value>选择一个选项:</value>
</data>
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
<value>键盘上下键移动光标,键盘回车键选择</value>
</data>
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
<value>输入数字并回车以选择选项</value>
</data>
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
<value>请输入 0 到 {0} 之间的数字</value>
</data>
</root>

View File

@@ -9,6 +9,7 @@ using Spectre.Console;
using YaeAchievement.Outputs;
using YaeAchievement.Parsers;
using YaeAchievement.res;
using YaeAchievement.Utilities;
// ReSharper disable UnusedMember.Local
@@ -33,8 +34,8 @@ public static class Export {
};
Action<AchievementAllDataNotify> action;
if (ExportTo == 114514) {
var prompt = new SelectionPrompt<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
action = targets[AnsiConsole.Prompt(prompt)];
var prompt = new SelectionPromptCompat<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
action = targets[prompt.Prompt()];
} else {
action = targets.ElementAtOrDefault(ExportTo).Value ?? ToCocogoat;
}
@@ -210,7 +211,7 @@ public static class Export {
}
}
public class WxApp1Root {
public sealed class WxApp1Root {
public string Key { get; init; } = null!;
@@ -223,7 +224,7 @@ public class WxApp1Root {
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public partial class WxApp1Serializer : JsonSerializerContext {
public sealed partial class WxApp1Serializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf, string key) => JsonSerializer.Serialize(new WxApp1Root {
Key = key,
@@ -231,8 +232,8 @@ public partial class WxApp1Serializer : JsonSerializerContext {
}, Default.WxApp1Root);
}
public record CocogoatResponse(string Key);
public sealed record CocogoatResponse(string Key);
[JsonSerializable(typeof(CocogoatResponse))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public partial class CocogoatResponseContext : JsonSerializerContext;
public sealed partial class CocogoatResponseContext : JsonSerializerContext;

View File

@@ -8,7 +8,7 @@ namespace YaeAchievement.Outputs;
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public class PaimonRoot {
public sealed class PaimonRoot {
public Dictionary<uint, Dictionary<uint, bool>> Achievement { get; set; } = null!;
@@ -29,7 +29,7 @@ public class PaimonRoot {
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public partial class PaimonSerializer : JsonSerializerContext {
public sealed partial class PaimonSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.PaimonRoot.FromNotify(ntf), Default.PaimonRoot);

View File

@@ -8,9 +8,9 @@ namespace YaeAchievement.Outputs;
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public class SeelieRoot {
public sealed class SeelieRoot {
public class AchievementFinishStatus {
public sealed class AchievementFinishStatus {
public bool Done => true;
@@ -31,7 +31,7 @@ public class SeelieRoot {
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public partial class SeelieSerializer : JsonSerializerContext {
public sealed partial class SeelieSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.SeelieRoot.FromNotify(ntf), Default.SeelieRoot);

View File

@@ -9,7 +9,7 @@ namespace YaeAchievement.Outputs;
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public class UApplicationInfo {
public sealed class UApplicationInfo {
public string ExportApp => "YaeAchievement";
@@ -21,7 +21,7 @@ public class UApplicationInfo {
}
public class UAchievementInfo {
public sealed class UAchievementInfo {
public uint Id { get; set; }
@@ -33,7 +33,7 @@ public class UAchievementInfo {
}
public class UIAFRoot {
public sealed class UIAFRoot {
public UApplicationInfo Info => new ();
@@ -57,7 +57,7 @@ public class UIAFRoot {
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public partial class UIAFSerializer : JsonSerializerContext {
public sealed partial class UIAFSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.UIAFRoot.FromNotify(ntf), Default.UIAFRoot);

View File

@@ -14,7 +14,7 @@ public enum AchievementStatus {
RewardTaken,
}
public class AchievementItem {
public sealed class AchievementItem {
public uint Id { get; init; }
public uint TotalProgress { get; init; }
@@ -24,7 +24,7 @@ public class AchievementItem {
}
public class AchievementAllDataNotify {
public sealed class AchievementAllDataNotify {
public List<AchievementItem> AchievementList { get; private init; } = [];
@@ -144,7 +144,7 @@ public class AchievementAllDataNotify {
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public partial class AchievementRawDataSerializer : JsonSerializerContext {
public sealed partial class AchievementRawDataSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(ntf, Default.AchievementAllDataNotify);

View File

@@ -9,7 +9,7 @@ using Spectre.Console;
namespace YaeAchievement.Parsers;
public class PlayerStoreNotify {
public sealed class PlayerStoreNotify {
public uint WeightLimit { get; set; }

View File

@@ -58,10 +58,10 @@ internal static class Program {
} catch (Exception) { /* ignored */ }
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
var prompt = new SelectionPrompt<string>()
var prompt = new SelectionPromptCompat<string>()
.Title(App.UsePreviousData)
.AddChoices(App.CommonYes, App.CommonNo);
if (AnsiConsole.Prompt(prompt) == App.CommonYes) {
if (prompt.Prompt() == App.CommonYes) {
Export.Choose(data);
return;
}
@@ -89,6 +89,7 @@ internal static class Program {
internal static void SetupConsole() {
SetQuickEditMode(false);
Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
FixTerminalFont();
}
}

View File

@@ -11,7 +11,7 @@ using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
namespace YaeAchievement.Utilities;
public unsafe class GameProcess {
public sealed unsafe class GameProcess {
public uint Id { get; }

View File

@@ -0,0 +1,48 @@
using Spectre.Console;
using YaeAchievement.res;
namespace YaeAchievement.Utilities;
public sealed class SelectionPromptCompat<T> where T : notnull {
private readonly List<T> _choices = [];
private readonly SelectionPrompt<T> _prompt = new ();
public SelectionPromptCompat<T> Title(string? title) {
_prompt.Title = title;
return this;
}
public SelectionPromptCompat<T> AddChoices(params IEnumerable<T> choices) {
foreach (var choice in choices) {
_prompt.AddChoice(choice);
_choices.Add(choice);
}
return this;
}
public T Prompt() {
if (AnsiConsole.Profile.Capabilities.Ansi) {
var title = _prompt.Title;
_prompt.Title += $" ({App.SelectionPromptCompatAnsiTip})";
var result = AnsiConsole.Prompt(_prompt);
_prompt.Title = title;
return result;
}
if (_prompt.Title != null) {
AnsiConsole.WriteLine(_prompt.Title + $" ({App.SelectionPromptCompatNonAnsiTip})");
}
for (var i = 0; i < _choices.Count; i++) {
var choice = _choices[i];
AnsiConsole.WriteLine($"[{i}] {choice}");
}
var choosePrompt = new TextPrompt<int>(App.SelectionPromptCompatChooseOne).Validate(i => {
if (i < 0 || i >= _choices.Count) {
return ValidationResult.Error(string.Format(App.SelectionPromptCompatInvalidChoice, _choices.Count - 1));
}
return ValidationResult.Success();
});
var resultIndex = AnsiConsole.Prompt(choosePrompt);
return _choices[resultIndex];
}
}

View File

@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO.Pipes;
using System.Net;
using System.Net.Http.Headers;
@@ -244,11 +245,27 @@ public static class Utils {
}).ContinueWith(task => { if (task.IsFaulted) OnUnhandledException(task.Exception!); });
}
public static unsafe void SetQuickEditMode(bool enable) {
internal static unsafe void SetQuickEditMode(bool enable) {
var handle = Native.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
CONSOLE_MODE mode = default;
Native.GetConsoleMode(handle, &mode);
mode = enable ? mode | CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE : mode &~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
Native.SetConsoleMode(handle, mode);
}
internal static unsafe void FixTerminalFont() {
if (!CultureInfo.CurrentCulture.Name.StartsWith("zh")) {
return;
}
var handle = Native.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
var fontInfo = new CONSOLE_FONT_INFOEX {
cbSize = (uint) sizeof(CONSOLE_FONT_INFOEX)
};
if (!Native.GetCurrentConsoleFontEx(handle, false, &fontInfo)) {
return;
}
if (fontInfo.FaceName.ToString() == "Terminal") { // 点阵字体
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); // todo: use better way like auto set console font etc.
}
}
}