[skip ci] export player store data

This commit is contained in:
HolographicHat
2025-01-11 19:24:42 +08:00
parent 2e2be07161
commit 32ceae074e
7 changed files with 174 additions and 25 deletions

View File

@@ -0,0 +1,74 @@
syntax = "proto3";
option csharp_namespace = "Proto";
enum StoreType {
STORE_TYPE_NONE = 0;
STORE_TYPE_PACK = 1;
STORE_TYPE_DEPOT = 2;
}
message MaterialDeleteInfo {
message CountDownDelete {
map<uint32, uint32> delete_time_num_map = 1;
uint32 config_count_down_time = 2;
}
message DateTimeDelete {
uint32 delete_time = 1;
}
message DelayWeekCountDownDelete {
map<uint32, uint32> delete_time_num_map = 1;
uint32 config_delay_week = 2;
uint32 config_count_down_time = 3;
}
bool has_delete_config = 1;
oneof delete_info {
CountDownDelete count_down_delete = 2;
DateTimeDelete date_delete = 3;
DelayWeekCountDownDelete delay_week_count_down_delete = 4;
}
}
message Material {
uint32 count = 1;
MaterialDeleteInfo delete_info = 2;
}
message Reliquary {
uint32 level = 1;
uint32 exp = 2;
uint32 promote_level = 3;
uint32 main_prop_id = 4;
repeated uint32 append_prop_id_list = 5;
bool is_marked = 6;
}
message Weapon {
uint32 level = 1;
uint32 exp = 2;
uint32 promote_level = 3;
map<uint32, uint32> affix_map = 4;
bool is_arkhe_ousia = 5;
}
message Equip {
oneof detail {
Reliquary reliquary = 1;
Weapon weapon = 2;
}
bool is_locked = 3;
}
message Furniture {
uint32 count = 1;
}
message Item {
uint32 item_id = 1;
uint64 guid = 2;
oneof detail {
Material material = 5;
Equip equip = 6;
Furniture furniture = 7;
}
}

View File

@@ -28,6 +28,8 @@ public static class GlobalVars {
public const string RinBucketHost = "https://rin.holohat.work";
public const string SakuraBucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
public static CacheFile AchievementDataCache { get; } = new ("achievement_data");
[field:MaybeNull]
public static AchievementInfo AchievementInfo =>
field ??= AchievementInfo.Parser.ParseFrom(Utils.GetBucketFile("schicksal/metadata").GetAwaiter().GetResult());
@@ -36,4 +38,5 @@ public static class GlobalVars {
Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(CachePath);
}
}

View File

@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using YaeAchievement.res;
@@ -27,6 +26,12 @@ public class AchievementAllDataNotify {
public List<AchievementItem> AchievementList { get; private init; } = [];
public static bool OnReceive(byte[] bytes) {
GlobalVars.AchievementDataCache.Write(bytes);
Export.Choose(ParseFrom(bytes));
return true;
}
public static AchievementAllDataNotify ParseFrom(byte[] bytes) {
using var stream = new CodedInputStream(bytes);
var data = new List<Dictionary<uint, uint>>();
@@ -36,7 +41,7 @@ public class AchievementAllDataNotify {
while ((tag = stream.ReadTag()) != 0) {
if ((tag & 7) == 2) { // is LengthDelimited
var dict = new Dictionary<uint, uint>();
using var eStream = new CodedInputStream(ReadRawBytes(stream, stream.ReadLength()));
using var eStream = stream.ReadLengthDelimitedAsStream();
try {
while ((tag = eStream.ReadTag()) != 0) {
if ((tag & 7) != 0) { // not VarInt
@@ -107,9 +112,6 @@ public class AchievementAllDataNotify {
};
}
[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern byte[] ReadRawBytes(CodedInputStream stream, int size);
}
[JsonSerializable(typeof(AchievementAllDataNotify))]

View File

@@ -0,0 +1,60 @@
using System.Text.Json;
using Google.Protobuf;
using Proto;
namespace YaeAchievement.Parsers;
public class PlayerStoreNotify {
public uint WeightLimit { get; set; }
public StoreType StoreType { get; set; }
public List<Item> ItemList { get; set; } = [];
public static bool OnReceive(byte[] bytes) {
#if DEBUG
var ntf = ParseFrom(bytes);
File.WriteAllText("store_data.json", JsonSerializer.Serialize(ntf, new JsonSerializerOptions {
WriteIndented = true
}));
#endif
return true;
}
private static PlayerStoreNotify ParseFrom(byte[] bytes) {
using var stream = new CodedInputStream(bytes);
var ntf = new PlayerStoreNotify();
try {
uint tag;
while ((tag = stream.ReadTag()) != 0) {
var wireType = tag & 7;
switch (wireType) {
case 0: { // is VarInt
var value = stream.ReadUInt32();
if (value < 10) {
ntf.StoreType = (StoreType) value;
} else {
ntf.WeightLimit = value;
}
continue;
}
case 2: { // is LengthDelimited
using var eStream = stream.ReadLengthDelimitedAsStream();
while (eStream.PeekTag() != 0) {
ntf.ItemList.Add(Item.Parser.ParseFrom(eStream));
}
break;
}
}
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
Console.WriteLine("Parse failed");
File.WriteAllBytes("store_raw_data.bin", bytes);
Environment.Exit(0);
}
return ntf;
}
}

View File

@@ -24,7 +24,7 @@ Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
await CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
var historyCache = new CacheFile("ExportData");
var historyCache = GlobalVars.AchievementDataCache;
AchievementAllDataNotify? data = null;
try {
@@ -39,11 +39,7 @@ if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null)
}
}
StartAndWaitResult(AppConfig.GamePath, str => {
GlobalVars.UnexpectedExit = false;
var bytes = Convert.FromBase64String(str);
var list = AchievementAllDataNotify.ParseFrom(bytes);
historyCache.Write(bytes);
Export.Choose(list);
return true;
StartAndWaitResult(AppConfig.GamePath, new Dictionary<byte, Func<byte[], bool>> {
{ 1, AchievementAllDataNotify.OnReceive },
{ 2, PlayerStoreNotify.OnReceive }
});

View File

@@ -0,0 +1,16 @@
using System.Runtime.CompilerServices;
// ReSharper disable CheckNamespace
namespace Google.Protobuf;
public static class CodedInputStreamExtensions {
[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern byte[] ReadRawBytes(CodedInputStream stream, int size);
public static CodedInputStream ReadLengthDelimitedAsStream(this CodedInputStream stream) {
return new CodedInputStream(ReadRawBytes(stream, stream.ReadLength()));
}
}

View File

@@ -114,7 +114,7 @@ public static class Utils {
Console.WriteLine(@"[DEBUG] Use local native lib.");
File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true);
} else if (info.EnableLibDownload) {
var data = await GetBucketFile("schicksal/lib.dll");
var data = await GetBucketFile("schicksal/lic.dll");
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data);
}
_updateInfo = info;
@@ -208,7 +208,7 @@ public static class Utils {
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
public static Thread StartAndWaitResult(string exePath, Dictionary<byte, Func<byte[], bool>> handlers) {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
try {
File.Delete(GlobalVars.LibFilePath);
@@ -228,7 +228,7 @@ public static class Utils {
proc = Process.GetProcessById(Convert.ToInt32(pid));
proc.EnableRaisingEvents = true;
proc.Exited += (_, _) => {
if (GlobalVars.UnexpectedExit)
if (handlers.Count != 0)
{
proc = null;
Console.WriteLine(App.GameProcessExit);
@@ -252,15 +252,13 @@ public static class Utils {
var ts = new ThreadStart(() => {
var server = new NamedPipeServerStream(GlobalVars.PipeName);
server.WaitForConnection();
using var reader = new StreamReader(server);
using var reader = new BinaryReader(server);
while (!proc.HasExited) {
var line = reader.ReadLine();
if (line?.Length > 0) {
if (onReceive(line)) {
break;
}
server.Disconnect();
server.WaitForConnection();
var type = reader.ReadByte();
var length = reader.ReadInt32(); // huh
var data = reader.ReadBytes(length);
if (handlers.Remove(type, out var handler)) {
handler(data);
}
}
});