diff --git a/src/Starward.Language/Lang.Designer.cs b/src/Starward.Language/Lang.Designer.cs
index d42683ce9..37358e344 100644
--- a/src/Starward.Language/Lang.Designer.cs
+++ b/src/Starward.Language/Lang.Designer.cs
@@ -1951,7 +1951,16 @@ public static string GachaImportAndExportWindow_Count {
return ResourceManager.GetString("GachaImportAndExportWindow_Count", resourceCulture);
}
}
-
+
+ ///
+ /// 查找类似 Archive Type 的本地化字符串。
+ ///
+ public static string GachaImportAndExportWindow_ArchiveType {
+ get {
+ return ResourceManager.GetString("GachaImportAndExportWindow_ArchiveType", resourceCulture);
+ }
+ }
+
///
/// 查找类似 Export 的本地化字符串。
///
@@ -2214,14 +2223,32 @@ public static string GachaLogPage_ImportAndExport {
}
///
- /// 查找类似 Import from JSON 的本地化字符串。
+ /// 查找类似 Import from {0} 的本地化字符串。
///
- public static string GachaLogPage_ImportFromJson {
+ public static string GachaLogPage_ImportFrom0 {
get {
- return ResourceManager.GetString("GachaLogPage_ImportFromJson", resourceCulture);
+ return ResourceManager.GetString("GachaLogPage_ImportFrom0", resourceCulture);
}
}
-
+
+ ///
+ /// 查找类似 UIGF v3.0 does not support Miliastra Wonderland Ode 的本地化字符串。
+ ///
+ public static string GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOde {
+ get {
+ return ResourceManager.GetString("GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOde", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 This account has Miliastra Wonderland Ode records, but the UIGF v3.0 format cannot carry them. 的本地化字符串。
+ ///
+ public static string GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOdeDesc {
+ get {
+ return ResourceManager.GetString("GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOdeDesc", resourceCulture);
+ }
+ }
+
///
/// 查找类似 Input URL 的本地化字符串。
///
diff --git a/src/Starward.Language/Lang.resx b/src/Starward.Language/Lang.resx
index 131330879..7120a3edd 100644
--- a/src/Starward.Language/Lang.resx
+++ b/src/Starward.Language/Lang.resx
@@ -343,8 +343,14 @@ closing the program during decompression will cause game file corruption.
Export as
-
- Import from JSON
+
+ Import from {0}
+
+
+ UIGF v3.0 does not support Miliastra Wonderland Ode
+
+
+ This account has Miliastra Wonderland Ode records, but the UIGF v3.0 format cannot carry them. If you continue, those records will be left out of the exported file. To export them, use UIGF v4.2 instead.
Show Novice Gacha Type
@@ -1917,6 +1923,9 @@ You are welcome to submit the exported data to the following repository:
Count
+
+ Archive Type
+
Time (Server Time Zone)
diff --git a/src/Starward.Language/Lang.zh-CN.resx b/src/Starward.Language/Lang.zh-CN.resx
index bbc6bd77f..55a6bfc72 100644
--- a/src/Starward.Language/Lang.zh-CN.resx
+++ b/src/Starward.Language/Lang.zh-CN.resx
@@ -343,8 +343,14 @@
导出为
-
- 从 JSON 导入
+
+ 从 {0} 导入
+
+
+ UIGF v3.0 不支持千星奇域
+
+
+ 当前账号存在千星奇域抽卡记录,但 UIGF v3.0 格式无法承载这部分数据。若选择继续,导出的文件将不包含千星奇域记录。如需导出,请改用 UIGF v4.2。
显示新手卡池
@@ -1917,6 +1923,9 @@
数量
+
+ 档案类型
+
时间 (服务器时区)
diff --git a/src/Starward/Features/Gacha/GachaLogPage.xaml b/src/Starward/Features/Gacha/GachaLogPage.xaml
index 892827ba8..e382b6ec3 100644
--- a/src/Starward/Features/Gacha/GachaLogPage.xaml
+++ b/src/Starward/Features/Gacha/GachaLogPage.xaml
@@ -209,10 +209,10 @@
HorizontalAlignment="Stretch"
Command="{x:Bind ExportGachaLogCommand}"
CommandParameter="json"
- Content="Json"
+ Content="{x:Bind LegacyFormatName}"
CornerRadius="0,4,4,0" />
-
+
-
+
+
+
+ Content="UIGF v4.2" />
diff --git a/src/Starward/Features/Gacha/GachaLogPage.xaml.cs b/src/Starward/Features/Gacha/GachaLogPage.xaml.cs
index 87ee03912..a1856b23e 100644
--- a/src/Starward/Features/Gacha/GachaLogPage.xaml.cs
+++ b/src/Starward/Features/Gacha/GachaLogPage.xaml.cs
@@ -40,6 +40,8 @@ public sealed partial class GachaLogPage : PageBase
private readonly GameRecordService _gameRecordService = AppConfig.GetService();
+ private readonly GenshinBeyondGachaService _genshinBeyondGachaService = AppConfig.GetService();
+
private GachaLogService _gachaLogService;
@@ -56,6 +58,15 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
GachaTypeText = GachaLogService.GetGachaLogText(CurrentGameBiz);
+ // 旧版导出对应的标准:原神是 UIGF v3.0,星铁是 SRGF v1.0,
+ // 绝区零没有对应标准,Starward 只是套用了同样的结构,故仍标为 JSON
+ LegacyFormatName = CurrentGameBiz.Game switch
+ {
+ GameBiz.hk4e => "UIGF v3.0",
+ GameBiz.hkrpg => "SRGF v1.0",
+ _ => "JSON",
+ };
+ LegacyImportText = string.Format(Lang.GachaLogPage_ImportFrom0, LegacyFormatName);
if (CurrentGameBiz.Game == GameBiz.hk4e)
{
EnableGenshinGachaItemStats = true;
@@ -99,6 +110,18 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
public string GachaTypeText { get; set => SetProperty(ref field, value); }
+ ///
+ /// 旧版(非 UIGF v4.x)导出格式名称,随游戏变化
+ ///
+ public string LegacyFormatName { get; set => SetProperty(ref field, value); } = "JSON";
+
+
+ ///
+ /// 旧版导入按钮文字,如「从 UIGF v3.0 导入」
+ ///
+ public string LegacyImportText { get; set => SetProperty(ref field, value); } = "";
+
+
public ObservableCollection UidList { get; set => SetProperty(ref field, value); }
@@ -985,6 +1008,10 @@ private async Task ExportGachaLogAsync(string format)
return;
}
long uid = SelectUid.Value;
+ if (!await EnsureExportWithoutGenshinBeyondAsync(uid))
+ {
+ return;
+ }
var ext = format switch
{
"excel" => "xlsx",
@@ -1012,6 +1039,35 @@ private async Task ExportGachaLogAsync(string format)
+ ///
+ /// UIGF v3.0 不支持千星奇域。若当前 Uid 存在千星奇域记录,提示用户这部分记录不会被导出,
+ /// 返回 false 表示用户选择取消,中断本次导出。
+ ///
+ private async Task EnsureExportWithoutGenshinBeyondAsync(long uid)
+ {
+ if (CurrentGameBiz.Game != GameBiz.hk4e)
+ {
+ return true;
+ }
+ if (!_genshinBeyondGachaService.GetUids().Contains(uid))
+ {
+ return true;
+ }
+ var dialog = new ContentDialog
+ {
+ Title = Lang.GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOde,
+ Content = Lang.GachaLogPage_UIGF30DoesNotSupportMiliastraWonderlandOdeDesc,
+ PrimaryButtonText = Lang.Common_Continue,
+ SecondaryButtonText = Lang.Common_Cancel,
+ DefaultButton = ContentDialogButton.Secondary,
+ XamlRoot = this.XamlRoot,
+ };
+ return await dialog.ShowAsync() is ContentDialogResult.Primary;
+ }
+
+
+
+
[RelayCommand]
private async Task ImportGachaLogAsync()
{
diff --git a/src/Starward/Features/Gacha/UIGF/GachaUidArchiveDisplay.cs b/src/Starward/Features/Gacha/UIGF/GachaUidArchiveDisplay.cs
index f6733bf82..4bafbbda8 100644
--- a/src/Starward/Features/Gacha/UIGF/GachaUidArchiveDisplay.cs
+++ b/src/Starward/Features/Gacha/UIGF/GachaUidArchiveDisplay.cs
@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Starward.Core;
+using Starward.Core.Gacha.Genshin;
using Starward.Core.Gacha.StarRail;
using Starward.Core.Gacha.ZZZ;
using System;
@@ -15,6 +16,24 @@ public class GachaUidArchiveDisplay : ObservableObject
public string GameIcon { get; set; }
+ ///
+ /// 千星奇域与原神本体共用 Uid,需要单独成行,故用此标记区分
+ ///
+ public bool IsGenshinBeyond { get; set; }
+
+ ///
+ /// 列表中显示的抽卡记录名称,如「祈愿记录」「千星奇域颂愿」
+ ///
+ public string ArchiveName => IsGenshinBeyond
+ ? Lang.GenshinBeyondGachaPage_MiliastraWonderlandOde
+ : Game.Value switch
+ {
+ GameBiz.hk4e => Lang.GachaLogService_WishRecords,
+ GameBiz.hkrpg => Lang.GachaLogService_WarpRecords,
+ GameBiz.nap => Lang.GachaLogService_SignalSearchRecords,
+ _ => Game.ToGameName(),
+ };
+
public long Uid { get; set; }
public int Count { get; set; }
@@ -32,6 +51,8 @@ public class GachaUidArchiveDisplay : ObservableObject
public List? napList { get; set; }
+ public List? hk4eUgcList { get; set; }
+
public int Timezone
{
diff --git a/src/Starward/Features/Gacha/UIGF/UIGF4File.cs b/src/Starward/Features/Gacha/UIGF/UIGF4File.cs
index ab90fcaa4..42f2561e9 100644
--- a/src/Starward/Features/Gacha/UIGF/UIGF4File.cs
+++ b/src/Starward/Features/Gacha/UIGF/UIGF4File.cs
@@ -1,4 +1,4 @@
-using Starward.Core.Gacha;
+using Starward.Core.Gacha.Genshin;
using Starward.Core.Gacha.StarRail;
using Starward.Core.Gacha.ZZZ;
using System;
@@ -26,6 +26,13 @@ public class UIGF4File
public List>? napGachaArchives { get; set; }
+ ///
+ /// 千星奇域,UIGF v4.2 新增
+ ///
+ [JsonPropertyName("hk4e_ugc")]
+ public List>? hk4eUgcGachaArchives { get; set; }
+
+
public UIGF4File()
{
@@ -33,6 +40,7 @@ public UIGF4File()
hk4eGachaArchives = new();
hkrpgGachaArchives = new();
napGachaArchives = new();
+ hk4eUgcGachaArchives = new();
}
@@ -60,7 +68,7 @@ public class UIGF4FileInfo
[JsonPropertyName("version")]
- public string Version { get; set; } = "v4.0";
+ public string Version { get; set; } = "v4.2";
public UIGF4FileInfo()
@@ -75,7 +83,10 @@ public UIGF4FileInfo()
-public class UIGF4GachaArchive where T : GachaLogItem
+///
+/// 千星奇域的记录不继承 ,故此处不约束泛型参数
+///
+public class UIGF4GachaArchive
{
[JsonPropertyName("uid")]
@@ -87,8 +98,13 @@ public class UIGF4GachaArchive where T : GachaLogItem
public int Timezone { get; set; }
+ ///
+ /// 语言代码。标准未将其列入 required,但定义了 enum,空字符串不是合法取值,
+ /// 因此无法确定语言时必须整个省略该字段,而不能写 ""。
+ ///
[JsonPropertyName("lang")]
- public string Lang { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? Lang { get; set; }
[JsonPropertyName("list")]
diff --git a/src/Starward/Features/Gacha/UIGF/UIGF4GachaWindow.xaml b/src/Starward/Features/Gacha/UIGF/UIGF4GachaWindow.xaml
index 7bdc85772..978be9c44 100644
--- a/src/Starward/Features/Gacha/UIGF/UIGF4GachaWindow.xaml
+++ b/src/Starward/Features/Gacha/UIGF/UIGF4GachaWindow.xaml
@@ -23,7 +23,7 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}">
-
+
@@ -61,7 +61,7 @@
@@ -73,6 +73,10 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Uid" />
+
+
@@ -154,7 +161,7 @@
TextWrapping="Wrap" />
@@ -166,6 +173,10 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Uid" />
+
+
@@ -221,32 +233,36 @@
VerticalAlignment="Center"
Text="{x:Bind Uid}" />
+
-
-
-
-
-
- GetLocalGachaArchives()
List result =
[
.. GetLocalGachaArchivesForGenshin(),
+ .. GetLocalGachaArchivesForGenshinBeyond(),
.. GetLocalGachaArchivesForStarRail(),
.. GetLocalGachaArchivesForZZZ(),
];
@@ -72,6 +74,35 @@ private List GetLocalGachaArchivesForGenshin()
}
+ ///
+ /// 千星奇域,与原神本体共用 Uid,单独成行
+ ///
+ private List GetLocalGachaArchivesForGenshinBeyond()
+ {
+ using var dapper = DatabaseService.CreateConnection();
+ List result = new();
+ var uidList = dapper.Query($"SELECT DISTINCT Uid FROM GenshinBeyondGachaItem;");
+ foreach (long uid in uidList)
+ {
+ int count = dapper.QueryFirstOrDefault($"SELECT COUNT(*) FROM GenshinBeyondGachaItem WHERE Uid=@Uid;", new { Uid = uid });
+ GenshinBeyondGachaItem lastItem = dapper.QueryFirst($"SELECT * FROM GenshinBeyondGachaItem WHERE Uid=@Uid ORDER BY Time DESC LIMIT 1;", new { Uid = uid });
+ var display = new GachaUidArchiveDisplay
+ {
+ Game = GameBiz.hk4e,
+ GameIcon = "ms-appx:///Assets/Image/icon_ys.jpg",
+ IsGenshinBeyond = true,
+ Uid = uid,
+ Count = count,
+ LastItemGachaType = lastItem.OpGachaType is 1000 ? CoreLang.GachaType_StandardOde : CoreLang.GachaType_EventOde,
+ LastItemName = lastItem.ItemName,
+ LastItemTime = lastItem.Time
+ };
+ result.Add(display);
+ }
+ return result;
+ }
+
+
private List GetLocalGachaArchivesForStarRail()
{
using var dapper = DatabaseService.CreateConnection();
@@ -130,7 +161,14 @@ public async Task ExportUIGF4Async(string path, params IEnumerable GetUIGFGachaArchiveForGenshin(lo
{
Uid = uid,
List = list.ToList(),
- Lang = list.LastOrDefault()?.Lang ?? "",
+ Lang = list.LastOrDefault()?.Lang is { Length: > 0 } l ? l : null,
+ };
+ archive.Timezone = uid.ToString()[0] switch
+ {
+ '6' => -5,
+ '7' => 1,
+ _ => 8,
+ };
+ return archive;
+ }
+
+
+
+ ///
+ /// 千星奇域,UIGF v4.2 的 hk4e_ugc 区段。
+ /// 记录本身没有 lang 字段,故从同一 Uid 的原神本体记录推导,再退回到抽卡语言设置;
+ /// 两者都拿不到时留 null 整个省略该字段,避免写出 enum 不允许的空字符串。
+ /// region 与 is_up 是标准未定义的扩展字段,随记录一并写出以便无损往返。
+ ///
+ private UIGF4GachaArchive GetUIGFGachaArchiveForGenshinBeyond(long uid)
+ {
+ using var dapper = DatabaseService.CreateConnection();
+ IEnumerable list = dapper.Query($"SELECT * FROM GenshinBeyondGachaItem WHERE Uid=@Uid ORDER BY Id;", new { Uid = uid });
+ string? lang = dapper.QueryFirstOrDefault($"SELECT Lang FROM GenshinGachaItem WHERE Uid=@Uid AND Lang IS NOT NULL AND Lang <> '' ORDER BY Id DESC LIMIT 1;", new { Uid = uid });
+ if (string.IsNullOrWhiteSpace(lang))
+ {
+ lang = AppConfig.GachaLanguage;
+ }
+ UIGF4GachaArchive archive = new()
+ {
+ Uid = uid,
+ List = list.ToList(),
+ Lang = string.IsNullOrWhiteSpace(lang) ? null : LanguageUtil.FilterLanguage(lang),
};
archive.Timezone = uid.ToString()[0] switch
{
@@ -184,7 +254,7 @@ private UIGF4GachaArchive GetUIGFGachaArchiveForStarRail(long
{
Uid = uid,
List = list.ToList(),
- Lang = list.LastOrDefault()?.Lang ?? "",
+ Lang = list.LastOrDefault()?.Lang is { Length: > 0 } l ? l : null,
};
archive.Timezone = uid.ToString()[0] switch
{
@@ -205,7 +275,7 @@ private UIGF4GachaArchive GetUIGFGachaArchiveForZZZ(long uid)
{
Uid = uid,
List = list.ToList(),
- Lang = list.LastOrDefault()?.Lang ?? "",
+ Lang = list.LastOrDefault()?.Lang is { Length: > 0 } l ? l : null,
};
return archive;
}
@@ -246,6 +316,27 @@ public async Task> ImportFileAsync(string path)
list.Add(archive);
}
}
+ foreach (UIGF4GachaArchive item in uigf4Obj.hk4eUgcGachaArchives ?? [])
+ {
+ if (item.List.Count > 0)
+ {
+ GenshinBeyondGachaItem last = item.List.OrderBy(x => x.Id).Last();
+ GachaUidArchiveDisplay archive = new()
+ {
+ Game = GameBiz.hk4e,
+ GameIcon = "ms-appx:///Assets/Image/icon_ys.jpg",
+ IsGenshinBeyond = true,
+ Uid = item.Uid,
+ hk4eUgcList = item.List,
+ Count = item.List.Count,
+ LastItemGachaType = last.OpGachaType is 1000 ? CoreLang.GachaType_StandardOde : CoreLang.GachaType_EventOde,
+ LastItemName = last.ItemName,
+ LastItemTime = last.Time,
+ LastItemTimeOffest = last.Time,
+ };
+ list.Add(archive);
+ }
+ }
foreach (UIGF4GachaArchive item in uigf4Obj.hkrpgGachaArchives ?? [])
{
if (item.List.Count > 0)
@@ -303,7 +394,14 @@ public async Task ImportAsync(params IEnumerable archive
archive.Error = null;
if (archive.Game == GameBiz.hk4e)
{
- ImportForGenshin(archive);
+ if (archive.IsGenshinBeyond)
+ {
+ ImportForGenshinBeyond(archive);
+ }
+ else
+ {
+ ImportForGenshin(archive);
+ }
}
if (archive.Game == GameBiz.hkrpg)
{
@@ -380,6 +478,72 @@ INSERT OR REPLACE INTO GenshinGachaItem (Uid, Id, Name, Time, ItemId, ItemType,
+ ///
+ /// 千星奇域。region 与 is_up 是 UIGF 未定义的扩展字段,来自其他应用的文件可能没有,缺失时留空。
+ ///
+ private void ImportForGenshinBeyond(GachaUidArchiveDisplay archive)
+ {
+ List list = new();
+ DateTime TIME = new DateTime(2025, 10, 1);
+ bool noName = false;
+ foreach (GenshinBeyondGachaItem item in archive.hk4eUgcList ?? [])
+ {
+ if (item.Id == 0)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_0FieldIsMissingInAGachaRecord, "id"));
+ }
+ if (item.ItemId == 0)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_0FieldIsMissingInAGachaRecord, "item_id"));
+ }
+ if (item.OpGachaType == 0)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_0FieldIsMissingInAGachaRecord, "op_gacha_type"));
+ }
+ if (item.Time < TIME)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_0FieldIsMissingInAGachaRecord, "time"));
+ }
+ if (item.RankType == 0)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_0FieldIsMissingInAGachaRecord, "rank_type"));
+ }
+ if (string.IsNullOrWhiteSpace(item.ItemName))
+ {
+ noName = true;
+ }
+ if (item.Uid != 0 && item.Uid != archive.Uid)
+ {
+ throw new UIGF4ImportException(archive.Game, archive.Uid, string.Format(Lang.UIGFGachaService_UidMismatchDetectedExpected0ButFound1, archive.Uid, item.Uid));
+ }
+ list.Add(new GenshinBeyondGachaItem
+ {
+ Uid = archive.Uid,
+ Id = item.Id,
+ Region = item.Region,
+ OpGachaType = item.OpGachaType,
+ ScheduleId = item.ScheduleId,
+ ItemType = item.ItemType,
+ ItemId = item.ItemId,
+ ItemName = item.ItemName,
+ RankType = item.RankType,
+ IsUp = item.IsUp,
+ Time = item.Time.AddHours(archive.Timezone),
+ });
+ }
+ using var dapper = DatabaseService.CreateConnection();
+ using var t = dapper.BeginTransaction();
+ var affect = dapper.Execute("""
+ INSERT OR REPLACE INTO GenshinBeyondGachaItem (Uid, Id, Region, OpGachaType, ScheduleId, ItemType, ItemId, ItemName, RankType, IsUp, Time)
+ VALUES (@Uid, @Id, @Region, @OpGachaType, @ScheduleId, @ItemType, @ItemId, @ItemName, @RankType, @IsUp, @Time);
+ """, list, t);
+ t.Commit();
+ _logger.LogInformation("Imported {count} gacha records for {game} beyond.", affect, archive.Game);
+ archive.Result = noName ? Lang.UIGFGachaService_ImportSuccessfulButNoRecordItemName : Lang.UIGFGachaService_ImportSuccessful;
+ }
+
+
+
private void ImportForStarRail(GachaUidArchiveDisplay archive)
{
List list = new();
diff --git a/src/Starward/Features/Setting/ToolboxSetting.xaml.cs b/src/Starward/Features/Setting/ToolboxSetting.xaml.cs
index 2103adf96..46dd9f61f 100644
--- a/src/Starward/Features/Setting/ToolboxSetting.xaml.cs
+++ b/src/Starward/Features/Setting/ToolboxSetting.xaml.cs
@@ -31,7 +31,7 @@ protected override void OnLoaded()
null,
nameof(UIGF4GachaWindow),
nameof(Lang.ToolboxSetting_GachaRecordsImportExport),
- ""){ Description="UIGF v4.0" },
+ ""){ Description="UIGF v4.2" },
new ToolboxItem(null,
"ms-appx:///Assets/Image/GachaTicket2Big.png",
nameof(ZZZGachaInfoWindow),