using System; using System.Threading.Tasks; using Logic.CrashSight; using MemoryPack; using RuntimeData; #if STEAM_CHANNEL || STEAMWORKS_NET using Steamworks; #endif using TH1_Logic.Collect; using TH1_Logic.Config; using TH1_Logic.Core; using TH1_Logic.GameArchive; using TH1_Logic.Net; using TH1_Logic.Tools; using UnityEngine; namespace TH1_Logic.Oss { public class OssManager { public static OssManager Instance = new OssManager(); private const string FunctionUrl = "https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run"; private readonly StsTokenService _stsService; private readonly OssUploadService _uploadService; private StsCredentials _cachedCredentials; private DateTime _credentialsExpireTime; private StsCredentials _cachedCollectCredentials; private DateTime _collectCredentialsExpireTime; public const int MaxBugReportUploadBytes = PlayerBugReportService.MaxBugReportUploadBytes; public const int MaxMultilingualReportUploadBytes = PlayerMultilingualReportService.MaxMultilingualReportUploadBytes; private const int SteamAuthWarmupRetrySeconds = 30; private const int SteamAuthWarmupRefreshSeconds = 4 * 60; private DateTime _nextSteamAuthWarmupTime = DateTime.MinValue; private bool _isSteamAuthWarmupRunning; public OssManager() { _stsService = new StsTokenService(FunctionUrl); _uploadService = new OssUploadService(); } public void UploadMapData(string steamId, MapData endMap) { if (endMap.Net.Mode == NetMode.Multi && !LobbyManager.Instance.Lobby.IsLobbyOwner()) return; if (!GameArchiveManager.Instance.TryLoadCurrentBeginArchive(endMap.Net.Mode, out var beginMap)) { LogSystem.LogError($"UploadMapData Error beginMap is null : {endMap.Net.Mode} {endMap.MapID}"); return; } var ossData = new OssData(); ossData.StartMap = beginMap; ossData.Actions = endMap.Net.Actions; ossData.CollectData = CollectManager.Instance.CollectData; ossData.CollectData.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); byte[] bytes = TH1Serialization.Serialize(ossData); _ = UploadGameDataAsync(steamId, bytes); } public async Task UploadGameDataAsync(string steamId, byte[] data) { try { // 检查缓存的凭证是否有效(提前60秒刷新) if (_cachedCredentials == null || DateTime.UtcNow >= _credentialsExpireTime.AddSeconds(-60)) { if (!TryGetAuthTicket(out var authTicket)) return false; _cachedCredentials = await _stsService.RequestStsTokenAsync(steamId, authTicket, "ossdata"); _credentialsExpireTime = DateTime.UtcNow.AddSeconds(_cachedCredentials.expiresIn); LogSystem.LogInfo($"STS token obtained, expires at {_credentialsExpireTime}"); } var result = await _uploadService.UploadFileAsync(_cachedCredentials, data); if (result) { LogSystem.LogInfo($"Upload success: {_cachedCredentials.objectKey}"); } return result; } catch (Exception ex) { LogSystem.LogError($"Upload failed: {ex.Message}"); _cachedCredentials = null; return false; } } public void UploadCollectData(string steamId, MapData endMap) { if (endMap.Net.Mode == NetMode.Multi && !LobbyManager.Instance.Lobby.IsLobbyOwner()) return; var collectData = CollectManager.Instance.CollectData; collectData.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); collectData.Version = ConfigManager.Instance.VersionCfg.CurVersionInfo.Version; _ = UploadCollectDataAsync(steamId, collectData); } public async Task UploadCollectDataAsync(string steamId, CollectData collectData) { try { // 检查缓存的凭证是否有效(提前60秒刷新) if (_cachedCollectCredentials == null || DateTime.UtcNow >= _collectCredentialsExpireTime.AddSeconds(-60)) { if (!TryGetAuthTicket(out var authTicket)) return false; _cachedCollectCredentials = await _stsService.RequestStsTokenAsync(steamId, authTicket, "collectdata"); _collectCredentialsExpireTime = DateTime.UtcNow.AddSeconds(_cachedCollectCredentials.expiresIn); LogSystem.LogInfo($"Collect STS token obtained, expires at {_collectCredentialsExpireTime}"); } byte[] data = TH1Serialization.Serialize(collectData); var result = await _uploadService.UploadFileAsync(_cachedCollectCredentials, data); if (result) { LogSystem.LogInfo($"CollectData upload success: {_cachedCollectCredentials.objectKey}"); } return result; } catch (Exception ex) { LogSystem.LogError($"CollectData upload failed: {ex.Message}"); _cachedCollectCredentials = null; return false; } } public async Task<(bool success, string objectKey)> UploadPlayerBugReportAsync(string steamId, byte[] packageData, string version) { try { if (packageData == null || packageData.Length == 0) { LogSystem.LogError("PlayerBugReport upload failed: package data is empty"); return (false, null); } if (packageData.Length > MaxBugReportUploadBytes) { LogSystem.LogError($"PlayerBugReport upload failed: package size {packageData.Length} exceeds {MaxBugReportUploadBytes}"); return (false, null); } if (!TryGetAuthTicket(out var authTicket)) return (false, null); var credentials = await _stsService.RequestStsTokenAsync(steamId, authTicket, "bugreport", version); var result = await _uploadService.UploadFileAsync(credentials, packageData, "application/zip", MaxBugReportUploadBytes); if (result) { LogSystem.LogInfo($"PlayerBugReport upload success: {credentials.objectKey}"); return (true, credentials.objectKey); } return (false, credentials.objectKey); } catch (Exception ex) { LogSystem.LogError($"PlayerBugReport upload failed: {ex.Message}"); return (false, null); } } public async Task<(bool success, string objectKey)> UploadPlayerMultilingualReportAsync(string steamId, byte[] packageData, string version) { try { if (packageData == null || packageData.Length == 0) { LogSystem.LogError("PlayerMultilingualReport upload failed: package data is empty"); return (false, null); } if (packageData.Length > MaxMultilingualReportUploadBytes) { LogSystem.LogError( $"PlayerMultilingualReport upload failed: package size {packageData.Length} exceeds {MaxMultilingualReportUploadBytes}"); return (false, null); } if (!TryGetAuthTicket(out var authTicket)) return (false, null); var credentials = await _stsService.RequestStsTokenAsync(steamId, authTicket, "multilingualreport", version); var result = await _uploadService.UploadFileAsync(credentials, packageData, "application/zip", MaxMultilingualReportUploadBytes); if (result) { LogSystem.LogInfo($"PlayerMultilingualReport upload success: {credentials.objectKey}"); return (true, credentials.objectKey); } return (false, credentials.objectKey); } catch (Exception ex) { LogSystem.LogError($"PlayerMultilingualReport upload failed: {ex.Message}"); return (false, null); } } public void UpdateSteamAuthWarmup() { if (Logic.AI.AIDirectorBatchRuntime.SkipPresentationWait) return; if (_isSteamAuthWarmupRunning) return; if (DateTime.UtcNow < _nextSteamAuthWarmupTime) return; if (!TryGetSteamId(out var steamId)) { _nextSteamAuthWarmupTime = DateTime.UtcNow.AddSeconds(15); return; } _ = WarmupSteamAuthAsync(steamId); } public async Task WarmupSteamAuthAsync(string steamId) { _isSteamAuthWarmupRunning = true; try { if (!TryGetAuthTicket(out var authTicket)) return false; var response = await _stsService.WarmupSteamAuthAsync(steamId, authTicket); var refreshSeconds = response != null && response.expiresIn > 90 ? Math.Min(SteamAuthWarmupRefreshSeconds, response.expiresIn - 60) : SteamAuthWarmupRetrySeconds; _nextSteamAuthWarmupTime = DateTime.UtcNow.AddSeconds(refreshSeconds); LogSystem.LogInfo($"Steam auth warmup success, cached={response?.cached}, expiresIn={response?.expiresIn}s"); return true; } catch (Exception ex) { _nextSteamAuthWarmupTime = DateTime.UtcNow.AddSeconds(SteamAuthWarmupRetrySeconds); LogSystem.LogWarning($"Steam auth warmup failed: {ex.Message}"); return false; } finally { _isSteamAuthWarmupRunning = false; } } private static bool TryGetSteamId(out string steamId) { steamId = ""; #if STEAM_CHANNEL || STEAMWORKS_NET try { if (!SteamUser.BLoggedOn()) return false; var id = SteamUser.GetSteamID().m_SteamID; if (id == 0) return false; steamId = id.ToString(); return true; } catch { return false; } #else return false; #endif } private static bool TryGetAuthTicket(out string authTicket) { authTicket = null; #if STEAM_CHANNEL || STEAMWORKS_NET try { var ticket = new byte[1024]; var identity = new SteamNetworkingIdentity(); SteamUser.GetAuthSessionTicket(ticket, 1024, out var ticketSize, ref identity); if (ticketSize == 0) { LogSystem.LogWarning("Steam auth ticket is empty"); return false; } authTicket = BitConverter.ToString(ticket, 0, (int)ticketSize).Replace("-", "").ToLower(); return true; } catch (Exception ex) { LogSystem.LogWarning($"Steam auth ticket unavailable: {ex.Message}"); return false; } #else return false; #endif } } }