summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs16
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs185
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs215
3 files changed, 199 insertions, 217 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 15f1c163..8bb63299 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -101,9 +101,21 @@ namespace StardewModdingAPI.Framework
/// <summary>Get a new content manager which handles reading files from a SMAPI mod folder with support for unpacked files.</summary>
/// <param name="name">A name for the mod manager. Not guaranteed to be unique.</param>
/// <param name="rootDirectory">The root directory to search for content (or <c>null</c> for the default).</param>
- public ModContentManager CreateModContentManager(string name, string rootDirectory)
+ /// <param name="gameContentManager">The game content manager used for map tilesheets not provided by the mod.</param>
+ public ModContentManager CreateModContentManager(string name, string rootDirectory, IContentManager gameContentManager)
{
- ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.JsonHelper, this.OnDisposing);
+ ModContentManager manager = new ModContentManager(
+ name: name,
+ gameContentManager: gameContentManager,
+ serviceProvider: this.MainContentManager.ServiceProvider,
+ rootDirectory: rootDirectory,
+ currentCulture: this.MainContentManager.CurrentCulture,
+ coordinator: this,
+ monitor: this.Monitor,
+ reflection: this.Reflection,
+ jsonHelper: this.JsonHelper,
+ onDisposing: this.OnDisposing
+ );
this.ContentManagers.Add(manager);
return manager;
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 1df7d107..45303f6b 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -1,12 +1,19 @@
using System;
using System.Globalization;
using System.IO;
+using System.Linq;
using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Toolkit.Serialisation;
+using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
+using xTile;
+using xTile.Format;
+using xTile.ObjectModel;
+using xTile.Tiles;
namespace StardewModdingAPI.Framework.ContentManagers
{
@@ -19,12 +26,16 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
private readonly JsonHelper JsonHelper;
+ /// <summary>The game content manager used for map tilesheets not provided by the mod.</summary>
+ private readonly IContentManager GameContentManager;
+
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="name">A name for the mod manager. Not guaranteed to be unique.</param>
+ /// <param name="gameContentManager">The game content manager used for map tilesheets not provided by the mod.</param>
/// <param name="serviceProvider">The service provider to use to locate services.</param>
/// <param name="rootDirectory">The root directory to search for content.</param>
/// <param name="currentCulture">The current culture for which to localise content.</param>
@@ -33,9 +44,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
- public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing)
+ public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true)
{
+ this.GameContentManager = gameContentManager;
this.JsonHelper = jsonHelper;
}
@@ -47,11 +59,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
assetName = this.AssertAndNormaliseAssetName(assetName);
- // get from cache
+ // get managed asset
if (this.IsLoaded(assetName))
return base.Load<T>(assetName, language);
-
- // get managed asset
if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
{
if (contentManagerID != this.Name)
@@ -64,7 +74,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
return this.LoadManagedAsset<T>(assetName, contentManagerID, relativePath, language);
}
- throw new NotSupportedException("Can't load content folder asset from a mod content manager.");
+ // get local asset
+ string internalKey = this.GetInternalAssetKey(assetName);
+ if (this.IsLoaded(internalKey))
+ return base.Load<T>(internalKey, language);
+ return this.LoadManagedAsset<T>(internalKey, this.Name, assetName, this.Language);
}
/// <summary>Create a new content manager for temporary use.</summary>
@@ -73,6 +87,16 @@ namespace StardewModdingAPI.Framework.ContentManagers
throw new NotSupportedException("Can't create a temporary mod content manager.");
}
+ /// <summary>Get the underlying key in the game's content cache for an asset. This does not validate whether the asset exists.</summary>
+ /// <param name="key">The local path to a content file relative to the mod folder.</param>
+ /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
+ public string GetInternalAssetKey(string key)
+ {
+ FileInfo file = this.GetModFile(key);
+ string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName);
+ return Path.Combine(this.Name, relativePath);
+ }
+
/*********
** Private methods
@@ -133,7 +157,18 @@ namespace StardewModdingAPI.Framework.ContentManagers
// unpacked map
case ".tbin":
- throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper.");
+ // validate
+ if (typeof(T) != typeof(Map))
+ throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'.");
+
+ // fetch & cache
+ FormatManager formatManager = FormatManager.Instance;
+ Map map = formatManager.LoadMap(file.FullName);
+ this.FixCustomTilesheetPaths(map, relativeMapPath: relativePath);
+
+ // inject map
+ this.Inject(internalKey, map, this.Language);
+ return (T)(object)map;
default:
throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.json', '.png', '.tbin', or '.xnb'.");
@@ -186,5 +221,143 @@ namespace StardewModdingAPI.Framework.ContentManagers
texture.SetData(data);
return texture;
}
+
+ /// <summary>Fix custom map tilesheet paths so they can be found by the content manager.</summary>
+ /// <param name="map">The map whose tilesheets to fix.</param>
+ /// <param name="relativeMapPath">The relative map path within the mod folder.</param>
+ /// <exception cref="ContentLoadException">A map tilesheet couldn't be resolved.</exception>
+ /// <remarks>
+ /// The game's logic for tilesheets in <see cref="Game1.setGraphicsForSeason"/> is a bit specialised. It boils
+ /// down to this:
+ /// * If the location is indoors or the desert, or the image source contains 'path' or 'object', it's loaded
+ /// as-is relative to the <c>Content</c> folder.
+ /// * Else it's loaded from <c>Content\Maps</c> with a seasonal prefix.
+ ///
+ /// That logic doesn't work well in our case, mainly because we have no location metadata at this point.
+ /// Instead we use a more heuristic approach: check relative to the map file first, then relative to
+ /// <c>Content\Maps</c>, then <c>Content</c>. If the image source filename contains a seasonal prefix, try for a
+ /// seasonal variation and then an exact match.
+ ///
+ /// While that doesn't exactly match the game logic, it's close enough that it's unlikely to make a difference.
+ /// </remarks>
+ private void FixCustomTilesheetPaths(Map map, string relativeMapPath)
+ {
+ // get map info
+ if (!map.TileSheets.Any())
+ return;
+ relativeMapPath = this.AssertAndNormaliseAssetName(relativeMapPath); // Mono's Path.GetDirectoryName doesn't handle Windows dir separators
+ string relativeMapFolder = Path.GetDirectoryName(relativeMapPath) ?? ""; // folder path containing the map, relative to the mod folder
+ bool isOutdoors = map.Properties.TryGetValue("Outdoors", out PropertyValue outdoorsProperty) && outdoorsProperty != null;
+
+ // fix tilesheets
+ foreach (TileSheet tilesheet in map.TileSheets)
+ {
+ string imageSource = tilesheet.ImageSource;
+
+ // validate tilesheet path
+ if (Path.IsPathRooted(imageSource) || PathUtilities.GetSegments(imageSource).Contains(".."))
+ throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded. Tilesheet paths must be a relative path without directory climbing (../).");
+
+ // get seasonal name (if applicable)
+ string seasonalImageSource = null;
+ if (isOutdoors && Context.IsSaveLoaded && Game1.currentSeason != null)
+ {
+ string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null.");
+ bool hasSeasonalPrefix =
+ filename.StartsWith("spring_", StringComparison.CurrentCultureIgnoreCase)
+ || filename.StartsWith("summer_", StringComparison.CurrentCultureIgnoreCase)
+ || filename.StartsWith("fall_", StringComparison.CurrentCultureIgnoreCase)
+ || filename.StartsWith("winter_", StringComparison.CurrentCultureIgnoreCase);
+ if (hasSeasonalPrefix && !filename.StartsWith(Game1.currentSeason + "_"))
+ {
+ string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename, StringComparison.CurrentCultureIgnoreCase));
+ seasonalImageSource = $"{dirPath}{Game1.currentSeason}_{filename.Substring(filename.IndexOf("_", StringComparison.CurrentCultureIgnoreCase) + 1)}";
+ }
+ }
+
+ // load best match
+ try
+ {
+ string key =
+ this.GetTilesheetAssetName(relativeMapFolder, seasonalImageSource)
+ ?? this.GetTilesheetAssetName(relativeMapFolder, imageSource);
+ if (key != null)
+ {
+ tilesheet.ImageSource = key;
+ continue;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex);
+ }
+
+ // none found
+ throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.");
+ }
+ }
+
+ /// <summary>Get the actual asset name for a tilesheet.</summary>
+ /// <param name="modRelativeMapFolder">The folder path containing the map, relative to the mod folder.</param>
+ /// <param name="imageSource">The tilesheet image source to load.</param>
+ /// <returns>Returns the asset name.</returns>
+ /// <remarks>See remarks on <see cref="FixCustomTilesheetPaths"/>.</remarks>
+ private string GetTilesheetAssetName(string modRelativeMapFolder, string imageSource)
+ {
+ if (imageSource == null)
+ return null;
+
+ // check relative to map file
+ {
+ string localKey = Path.Combine(modRelativeMapFolder, imageSource);
+ FileInfo localFile = this.GetModFile(localKey);
+ if (localFile.Exists)
+ return this.GetInternalAssetKey(localKey);
+ }
+
+ // check relative to content folder
+ {
+ foreach (string candidateKey in new[] { imageSource, Path.Combine("Maps", imageSource) })
+ {
+ string contentKey = candidateKey.EndsWith(".png")
+ ? candidateKey.Substring(0, candidateKey.Length - 4)
+ : candidateKey;
+
+ try
+ {
+ this.GameContentManager.Load<Texture2D>(contentKey);
+ return contentKey;
+ }
+ catch
+ {
+ // ignore file-not-found errors
+ // TODO: while it's useful to suppress an asset-not-found error here to avoid
+ // confusion, this is a pretty naive approach. Even if the file doesn't exist,
+ // the file may have been loaded through an IAssetLoader which failed. So even
+ // if the content file doesn't exist, that doesn't mean the error here is a
+ // content-not-found error. Unfortunately XNA doesn't provide a good way to
+ // detect the error type.
+ if (this.GetContentFolderFileExists(contentKey))
+ throw;
+ }
+ }
+ }
+
+ // not found
+ return null;
+ }
+
+ /// <summary>Get whether a file from the game's content folder exists.</summary>
+ /// <param name="key">The asset key.</param>
+ private bool GetContentFolderFileExists(string key)
+ {
+ // get file path
+ string path = Path.Combine(this.GameContentManager.FullRootDirectory, key);
+ if (!path.EndsWith(".xnb"))
+ path += ".xnb";
+
+ // get file
+ return new FileInfo(path).Exists;
+ }
}
}
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 5c4c3bca..2d65f1f6 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -9,12 +9,8 @@ using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Exceptions;
-using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
using xTile;
-using xTile.Format;
-using xTile.ObjectModel;
-using xTile.Tiles;
namespace StardewModdingAPI.Framework.ModHelpers
{
@@ -31,10 +27,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
private readonly IContentManager GameContentManager;
/// <summary>A content manager for this mod which manages files from the mod's folder.</summary>
- private readonly IContentManager ModContentManager;
-
- /// <summary>The absolute path to the mod folder.</summary>
- private readonly string ModFolderPath;
+ private readonly ModContentManager ModContentManager;
/// <summary>The friendly mod name for use in errors.</summary>
private readonly string ModName;
@@ -79,8 +72,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
{
this.ContentCore = contentCore;
this.GameContentManager = contentCore.CreateGameContentManager(this.ContentCore.GetManagedAssetPrefix(modID) + ".content");
- this.ModContentManager = contentCore.CreateModContentManager(this.ContentCore.GetManagedAssetPrefix(modID), rootDirectory: modFolderPath);
- this.ModFolderPath = modFolderPath;
+ this.ModContentManager = contentCore.CreateModContentManager(this.ContentCore.GetManagedAssetPrefix(modID), modFolderPath, this.GameContentManager);
this.ModName = modName;
this.Monitor = monitor;
}
@@ -93,8 +85,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
public T Load<T>(string key, ContentSource source = ContentSource.ModFolder)
{
- SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: {reasonPhrase}.");
-
try
{
this.AssertAndNormaliseAssetName(key);
@@ -104,38 +94,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
return this.GameContentManager.Load<T>(key);
case ContentSource.ModFolder:
- // get file
- FileInfo file = this.GetModFile(key);
- if (!file.Exists)
- throw GetContentError($"there's no matching file at path '{file.FullName}'.");
- string internalKey = this.GetInternalModAssetKey(file);
-
- // try cache
- if (this.ModContentManager.IsLoaded(internalKey))
- return this.ModContentManager.Load<T>(internalKey);
-
- // fix map tilesheets
- if (file.Extension.ToLower() == ".tbin")
- {
- // validate
- if (typeof(T) != typeof(Map))
- throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'.");
-
- // fetch & cache
- FormatManager formatManager = FormatManager.Instance;
- Map map = formatManager.LoadMap(file.FullName);
- this.FixCustomTilesheetPaths(map, relativeMapPath: key);
-
- // inject map
- this.ModContentManager.Inject(internalKey, map, this.CurrentLocaleConstant);
- return (T)(object)map;
- }
-
- // load through content manager
- return this.ModContentManager.Load<T>(internalKey);
+ return this.ModContentManager.Load<T>(key);
default:
- throw GetContentError($"unknown content source '{source}'.");
+ throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'.");
}
}
catch (Exception ex) when (!(ex is SContentLoadException))
@@ -164,8 +126,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
return this.GameContentManager.AssertAndNormaliseAssetName(key);
case ContentSource.ModFolder:
- FileInfo file = this.GetModFile(key);
- return this.GetInternalModAssetKey(file);
+ return this.ModContentManager.GetInternalAssetKey(key);
default:
throw new NotSupportedException($"Unknown content source '{source}'.");
@@ -201,6 +162,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
return this.ContentCore.InvalidateCache(predicate).Any();
}
+
/*********
** Private methods
*********/
@@ -214,170 +176,5 @@ namespace StardewModdingAPI.Framework.ModHelpers
if (Path.IsPathRooted(key))
throw new ArgumentException("The asset key must not be an absolute path.");
}
-
- /// <summary>Get the internal key in the content cache for a mod asset.</summary>
- /// <param name="modFile">The asset file.</param>
- private string GetInternalModAssetKey(FileInfo modFile)
- {
- string relativePath = PathUtilities.GetRelativePath(this.ModFolderPath, modFile.FullName);
- return Path.Combine(this.ModContentManager.Name, relativePath);
- }
-
- /// <summary>Fix custom map tilesheet paths so they can be found by the content manager.</summary>
- /// <param name="map">The map whose tilesheets to fix.</param>
- /// <param name="relativeMapPath">The relative map path within the mod folder.</param>
- /// <exception cref="ContentLoadException">A map tilesheet couldn't be resolved.</exception>
- /// <remarks>
- /// The game's logic for tilesheets in <see cref="Game1.setGraphicsForSeason"/> is a bit specialised. It boils
- /// down to this:
- /// * If the location is indoors or the desert, or the image source contains 'path' or 'object', it's loaded
- /// as-is relative to the <c>Content</c> folder.
- /// * Else it's loaded from <c>Content\Maps</c> with a seasonal prefix.
- ///
- /// That logic doesn't work well in our case, mainly because we have no location metadata at this point.
- /// Instead we use a more heuristic approach: check relative to the map file first, then relative to
- /// <c>Content\Maps</c>, then <c>Content</c>. If the image source filename contains a seasonal prefix, try for a
- /// seasonal variation and then an exact match.
- ///
- /// While that doesn't exactly match the game logic, it's close enough that it's unlikely to make a difference.
- /// </remarks>
- private void FixCustomTilesheetPaths(Map map, string relativeMapPath)
- {
- // get map info
- if (!map.TileSheets.Any())
- return;
- relativeMapPath = this.ModContentManager.AssertAndNormaliseAssetName(relativeMapPath); // Mono's Path.GetDirectoryName doesn't handle Windows dir separators
- string relativeMapFolder = Path.GetDirectoryName(relativeMapPath) ?? ""; // folder path containing the map, relative to the mod folder
- bool isOutdoors = map.Properties.TryGetValue("Outdoors", out PropertyValue outdoorsProperty) && outdoorsProperty != null;
-
- // fix tilesheets
- foreach (TileSheet tilesheet in map.TileSheets)
- {
- string imageSource = tilesheet.ImageSource;
-
- // validate tilesheet path
- if (Path.IsPathRooted(imageSource) || PathUtilities.GetSegments(imageSource).Contains(".."))
- throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded. Tilesheet paths must be a relative path without directory climbing (../).");
-
- // get seasonal name (if applicable)
- string seasonalImageSource = null;
- if (isOutdoors && Context.IsSaveLoaded && Game1.currentSeason != null)
- {
- string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null.");
- bool hasSeasonalPrefix =
- filename.StartsWith("spring_", StringComparison.CurrentCultureIgnoreCase)
- || filename.StartsWith("summer_", StringComparison.CurrentCultureIgnoreCase)
- || filename.StartsWith("fall_", StringComparison.CurrentCultureIgnoreCase)
- || filename.StartsWith("winter_", StringComparison.CurrentCultureIgnoreCase);
- if (hasSeasonalPrefix && !filename.StartsWith(Game1.currentSeason + "_"))
- {
- string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename, StringComparison.CurrentCultureIgnoreCase));
- seasonalImageSource = $"{dirPath}{Game1.currentSeason}_{filename.Substring(filename.IndexOf("_", StringComparison.CurrentCultureIgnoreCase) + 1)}";
- }
- }
-
- // load best match
- try
- {
- string key =
- this.GetTilesheetAssetName(relativeMapFolder, seasonalImageSource)
- ?? this.GetTilesheetAssetName(relativeMapFolder, imageSource);
- if (key != null)
- {
- tilesheet.ImageSource = key;
- continue;
- }
- }
- catch (Exception ex)
- {
- throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex);
- }
-
- // none found
- throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.");
- }
- }
-
- /// <summary>Get the actual asset name for a tilesheet.</summary>
- /// <param name="modRelativeMapFolder">The folder path containing the map, relative to the mod folder.</param>
- /// <param name="imageSource">The tilesheet image source to load.</param>
- /// <returns>Returns the asset name.</returns>
- /// <remarks>See remarks on <see cref="FixCustomTilesheetPaths"/>.</remarks>
- private string GetTilesheetAssetName(string modRelativeMapFolder, string imageSource)
- {
- if (imageSource == null)
- return null;
-
- // check relative to map file
- {
- string localKey = Path.Combine(modRelativeMapFolder, imageSource);
- FileInfo localFile = this.GetModFile(localKey);
- if (localFile.Exists)
- return this.GetActualAssetKey(localKey);
- }
-
- // check relative to content folder
- {
- foreach (string candidateKey in new[] { imageSource, Path.Combine("Maps", imageSource) })
- {
- string contentKey = candidateKey.EndsWith(".png")
- ? candidateKey.Substring(0, candidateKey.Length - 4)
- : candidateKey;
-
- try
- {
- this.Load<Texture2D>(contentKey, ContentSource.GameContent);
- return contentKey;
- }
- catch
- {
- // ignore file-not-found errors
- // TODO: while it's useful to suppress an asset-not-found error here to avoid
- // confusion, this is a pretty naive approach. Even if the file doesn't exist,
- // the file may have been loaded through an IAssetLoader which failed. So even
- // if the content file doesn't exist, that doesn't mean the error here is a
- // content-not-found error. Unfortunately XNA doesn't provide a good way to
- // detect the error type.
- if (this.GetContentFolderFile(contentKey).Exists)
- throw;
- }
- }
- }
-
- // not found
- return null;
- }
-
- /// <summary>Get a file from the mod folder.</summary>
- /// <param name="path">The asset path relative to the mod folder.</param>
- private FileInfo GetModFile(string path)
- {
- // try exact match
- path = Path.Combine(this.ModFolderPath, this.ModContentManager.NormalisePathSeparators(path));
- FileInfo file = new FileInfo(path);
-
- // try with default extension
- if (!file.Exists && file.Extension.ToLower() != ".xnb")
- {
- FileInfo result = new FileInfo(path + ".xnb");
- if (result.Exists)
- file = result;
- }
-
- return file;
- }
-
- /// <summary>Get a file from the game's content folder.</summary>
- /// <param name="key">The asset key.</param>
- private FileInfo GetContentFolderFile(string key)
- {
- // get file path
- string path = Path.Combine(this.GameContentManager.FullRootDirectory, key);
- if (!path.EndsWith(".xnb"))
- path += ".xnb";
-
- // get file
- return new FileInfo(path);
- }
}
}