using System; using System.Collections; using System.Collections.Generic; using System.IO; using GameServer.Client; using UnityEngine; using UnityEngine.Networking; public static class DlcRemoteManifestSyncService { [Serializable] private sealed class RemoteManifestEnvelope { public bool success; public string message; public RemoteManifestItem[] dlcs; } [Serializable] private sealed class RemoteManifestItem { public string dlc_key; public string display_name; public string version; public string manifest_file_name; public string manifest_json; public string uploaded_at; public string updated_at; } private sealed class DlcRemoteManifestSyncRunner : MonoBehaviour { private void Start() { if (autoSyncOnStart) { SyncPublishedDlcs(); } } } private const string DefaultServerUrl = "https://game.bansonic.top"; private const string RemoteManifestFilePrefix = "remote_"; private const string ManifestApiPath = "/api/dlcs/manifests"; private static DlcRemoteManifestSyncRunner runner; private static bool autoSyncOnStart = true; private static bool syncRequestedWhileBusy; private static bool initialSyncPending = true; public static bool IsSyncing { get; private set; } public static event Action SyncCompleted; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void EnsureRunner() { if (runner != null) { return; } GameObject go = new GameObject(nameof(DlcRemoteManifestSyncService)); UnityEngine.Object.DontDestroyOnLoad(go); runner = go.AddComponent(); } public static void SetAutoSyncOnStart(bool enabled) { autoSyncOnStart = enabled; if (!enabled) { initialSyncPending = false; } } public static bool ShouldDeferInitialRefresh() { return autoSyncOnStart && initialSyncPending && !OnlineModeSettings.IsLocalOnlyMode; } public static void SyncPublishedDlcs() { EnsureRunner(); if (runner == null) { return; } if (IsSyncing) { syncRequestedWhileBusy = true; return; } runner.StartCoroutine(SyncPublishedDlcsRoutine()); } private static IEnumerator SyncPublishedDlcsRoutine() { if (IsSyncing) { yield break; } if (OnlineModeSettings.IsLocalOnlyMode) { initialSyncPending = false; DlcRemoteContentService.RefreshInstalledDlc(); SyncCompleted?.Invoke(false); yield break; } string url = BuildManifestApiUrl(); if (string.IsNullOrWhiteSpace(url)) { initialSyncPending = false; DlcRemoteContentService.RefreshInstalledDlc(); SyncCompleted?.Invoke(false); yield break; } IsSyncing = true; syncRequestedWhileBusy = false; bool success = false; using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.timeout = 10; yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty; success = TryApplyRemoteManifestPayload(responseText); } else { Debug.LogWarning("[DLC] Remote manifest sync failed: " + request.error); } } initialSyncPending = false; if (success) { DlcRemoteContentService.RefreshInstalledDlc(); } else { DlcRemoteContentService.RefreshInstalledDlc(); } IsSyncing = false; SyncCompleted?.Invoke(success); if (syncRequestedWhileBusy) { SyncPublishedDlcs(); } } private static bool TryApplyRemoteManifestPayload(string json) { if (string.IsNullOrWhiteSpace(json)) { return false; } RemoteManifestEnvelope envelope; try { envelope = JsonUtility.FromJson(json); } catch (Exception ex) { Debug.LogWarning("[DLC] Failed to parse remote manifest payload: " + ex.Message); return false; } if (envelope == null || !envelope.success) { Debug.LogWarning("[DLC] Remote manifest payload reported failure."); return false; } string manifestRoot = Path.Combine(Application.persistentDataPath, "DLC", "manifests"); try { Directory.CreateDirectory(manifestRoot); DeleteExistingRemoteManifestFiles(manifestRoot); RemoteManifestItem[] items = envelope.dlcs ?? Array.Empty(); for (int i = 0; i < items.Length; i++) { RemoteManifestItem item = items[i]; if (item == null || string.IsNullOrWhiteSpace(item.manifest_json)) { continue; } string safeKey = SanitizeFileName(item.dlc_key); if (string.IsNullOrWhiteSpace(safeKey)) { safeKey = "package_" + i; } string fileName = RemoteManifestFilePrefix + safeKey + ".json"; string path = Path.Combine(manifestRoot, fileName); File.WriteAllText(path, item.manifest_json); } return true; } catch (Exception ex) { Debug.LogWarning("[DLC] Failed to write remote manifests: " + ex.Message); return false; } } private static void DeleteExistingRemoteManifestFiles(string manifestRoot) { string[] files = Directory.GetFiles(manifestRoot, RemoteManifestFilePrefix + "*.json", SearchOption.TopDirectoryOnly); for (int i = 0; i < files.Length; i++) { try { File.Delete(files[i]); } catch (Exception ex) { Debug.LogWarning("[DLC] Failed to delete old remote manifest '" + files[i] + "': " + ex.Message); } } } private static string BuildManifestApiUrl() { string baseUrl = DefaultServerUrl; NetworkManager manager = NetworkManager.Instance; if (manager != null && !string.IsNullOrWhiteSpace(manager.ServerUrl)) { baseUrl = manager.ServerUrl.Trim(); } if (string.IsNullOrWhiteSpace(baseUrl)) { return string.Empty; } return baseUrl.TrimEnd('/') + ManifestApiPath; } private static string SanitizeFileName(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } char[] invalidChars = Path.GetInvalidFileNameChars(); string result = value.Trim(); for (int i = 0; i < invalidChars.Length; i++) { result = result.Replace(invalidChars[i], '_'); } return result.Replace(' ', '_'); } }