using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework { /// Manages access to a content pack's metadata and files. internal class ContentPack : IContentPack { /********* ** Fields *********/ /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; /// A case-insensitive lookup of relative paths within the . private readonly CaseInsensitivePathLookup RelativePathCache; /********* ** Accessors *********/ /// public string DirectoryPath { get; } /// public IManifest Manifest { get; } /// public ITranslationHelper Translation => this.TranslationImpl; /// public IModContentHelper ModContent { get; } /// The underlying translation helper. internal TranslationHelper TranslationImpl { get; set; } /********* ** Public methods *********/ /// Construct an instance. /// The full path to the content pack's folder. /// The content pack's manifest. /// Provides an API for loading content assets from the content pack's folder. /// Provides translations stored in the content pack's i18n folder. /// Encapsulates SMAPI's JSON file parsing. /// A case-insensitive lookup of relative paths within the . public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, CaseInsensitivePathLookup relativePathCache) { this.DirectoryPath = directoryPath; this.Manifest = manifest; this.ModContent = content; this.TranslationImpl = translation; this.JsonHelper = jsonHelper; this.RelativePathCache = relativePathCache; } /// public bool HasFile(string path) { path = PathUtilities.NormalizePath(path); return this.GetFile(path).Exists; } /// public TModel? ReadJsonFile(string path) where TModel : class { path = PathUtilities.NormalizePath(path); FileInfo file = this.GetFile(path); return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel? model) ? model : null; } /// public void WriteJsonFile(string path, TModel data) where TModel : class { path = PathUtilities.NormalizePath(path); FileInfo file = this.GetFile(path, out path); this.JsonHelper.WriteJsonFile(file.FullName, data); this.RelativePathCache.Add(path); } /// [Obsolete] public T LoadAsset(string key) where T : notnull { return this.ModContent.Load(key); } /// [Obsolete] public string GetActualAssetKey(string key) { return this.ModContent.GetInternalAssetName(key).Name; } /********* ** Private methods *********/ /// Get the underlying file info. /// The normalized file path relative to the content pack directory. private FileInfo GetFile(string relativePath) { return this.GetFile(relativePath, out _); } /// Get the underlying file info. /// The normalized file path relative to the content pack directory. /// The relative path after case-insensitive matching. private FileInfo GetFile(string relativePath, out string actualRelativePath) { if (!PathUtilities.IsSafeRelativePath(relativePath)) throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path."); actualRelativePath = this.RelativePathCache.GetFilePath(relativePath); return new FileInfo(Path.Combine(this.DirectoryPath, actualRelativePath)); } } }