using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities.PathLookups; 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 lookup for files within the . private readonly IFileLookup FileLookup; /********* ** 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 lookup for files within the . public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, IFileLookup fileLookup) { this.DirectoryPath = directoryPath; this.Manifest = manifest; this.ModContent = content; this.TranslationImpl = translation; this.JsonHelper = jsonHelper; this.FileLookup = fileLookup; } /// 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); bool didExist = file.Exists; this.JsonHelper.WriteJsonFile(file.FullName, data); if (!didExist) { this.FileLookup.Add( Path.GetRelativePath(this.DirectoryPath, file.FullName) ); } } /// [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")] public T LoadAsset(string key) where T : notnull { return this.ModContent.Load(key); } /// [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")] 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) { if (!PathUtilities.IsSafeRelativePath(relativePath)) throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path."); return this.FileLookup.GetFile(relativePath); } } }