Files

233 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class DlcManifestService
{
private static readonly string[] ManifestFilePatterns =
{
"*.json"
};
private const string RuntimeFolderName = "DLC";
private const string ManifestFolderName = "manifests";
public static List<DlcManifestRuntimeEntry> LoadInstalledEntries()
{
DlcPackageArchiveService.PrepareInstalledPackages();
List<DlcManifestRuntimeEntry> result = new List<DlcManifestRuntimeEntry>();
HashSet<string> visitedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string[] roots = GetManifestSearchRoots();
for (int i = 0; i < roots.Length; i++)
{
string root = roots[i];
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
{
continue;
}
CollectManifestEntries(root, result, visitedFiles);
}
return result;
}
public static string[] GetManifestSearchRoots()
{
List<string> result = new List<string>();
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName));
#if !UNITY_ANDROID
// 项目外(可执行文件同级目录)的 DLC 扫描仅在 PC 端有意义;
// 安卓无此目录概念,Application.dataPath 指向 APK,跳过以避免无效/异常路径。
string playerRoot = GetPlayerRootDirectory();
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName));
#endif
return result.ToArray();
}
public static DlcManifestFile ParseManifestJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return null;
}
try
{
return JsonUtility.FromJson<DlcManifestFile>(json);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to parse manifest json: " + ex.Message);
return null;
}
}
private static void CollectManifestEntries(
string root,
List<DlcManifestRuntimeEntry> result,
HashSet<string> visitedFiles)
{
for (int patternIndex = 0; patternIndex < ManifestFilePatterns.Length; patternIndex++)
{
string pattern = ManifestFilePatterns[patternIndex];
string[] files;
try
{
files = Directory.GetFiles(root, pattern, SearchOption.AllDirectories);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to enumerate manifest files in '" + root + "': " + ex.Message);
continue;
}
for (int i = 0; i < files.Length; i++)
{
string manifestPath = files[i];
if (string.IsNullOrWhiteSpace(manifestPath))
{
continue;
}
string fullPath = Path.GetFullPath(manifestPath);
if (!visitedFiles.Add(fullPath))
{
continue;
}
TryLoadManifestFile(fullPath, result);
}
}
}
private static void TryLoadManifestFile(string manifestPath, List<DlcManifestRuntimeEntry> result)
{
try
{
string json = File.ReadAllText(manifestPath);
DlcManifestFile file = ParseManifestJson(json);
if (file == null || file.entries == null || file.entries.Count == 0)
{
return;
}
string manifestDirectory = Path.GetDirectoryName(manifestPath) ?? string.Empty;
string manifestId = string.IsNullOrWhiteSpace(file.manifestId)
? Path.GetFileNameWithoutExtension(manifestPath)
: file.manifestId.Trim();
for (int i = 0; i < file.entries.Count; i++)
{
DlcManifestEntry entry = file.entries[i];
if (entry == null || !entry.enabled)
{
continue;
}
string resolvedCatalogPath = ResolveCatalogPath(entry.catalogPath, manifestDirectory);
if (string.IsNullOrWhiteSpace(resolvedCatalogPath))
{
Debug.LogWarning("[DLC] Manifest entry '" + manifestId + "' has no valid catalogPath.");
continue;
}
result.Add(new DlcManifestRuntimeEntry
{
manifestId = manifestId,
manifestSourcePath = manifestPath,
manifestDirectory = manifestDirectory,
entry = entry,
resolvedCatalogPath = resolvedCatalogPath
});
}
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to load manifest '" + manifestPath + "': " + ex.Message);
}
}
private static string ResolveCatalogPath(string catalogPath, string manifestDirectory)
{
if (string.IsNullOrWhiteSpace(catalogPath))
{
return string.Empty;
}
string trimmed = catalogPath.Trim();
if (trimmed.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
trimmed.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
trimmed.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
string combined = trimmed;
if (!Path.IsPathRooted(combined))
{
combined = Path.Combine(manifestDirectory ?? string.Empty, combined);
}
try
{
return new Uri(Path.GetFullPath(combined)).AbsoluteUri;
}
catch
{
return string.Empty;
}
}
private static string GetPlayerRootDirectory()
{
try
{
string dataPath = Application.dataPath;
if (string.IsNullOrWhiteSpace(dataPath))
{
return string.Empty;
}
DirectoryInfo parent = Directory.GetParent(dataPath);
return parent != null ? parent.FullName : string.Empty;
}
catch
{
return string.Empty;
}
}
private static void AppendIfValid(List<string> result, string path)
{
if (result == null || string.IsNullOrWhiteSpace(path))
{
return;
}
string fullPath;
try
{
fullPath = Path.GetFullPath(path);
}
catch
{
return;
}
if (!result.Contains(fullPath))
{
result.Add(fullPath);
}
}
}