From a91e111247cbe5d9b92953a7f449a12c6fa7b4cf Mon Sep 17 00:00:00 2001 From: Platonymous Date: Mon, 15 May 2017 06:56:51 +0200 Subject: Added experimental .tbin support --- src/StardewModdingAPI/Framework/ContentHelper.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index 893fa2c8..5a2a8b9b 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -6,6 +6,9 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewValley; +using xTile; +using xTile.Format; +using xTile.Tiles; namespace StardewModdingAPI.Framework { @@ -74,6 +77,23 @@ namespace StardewModdingAPI.Framework case ".xnb": return this.ContentManager.Load(assetPath); + case ".tbin": + // validate + if (typeof(T) != typeof(Map)) + throw new ContentLoadException($"Can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); + + // try cache + if (this.ContentManager.IsLoaded(assetPath)) + return this.ContentManager.Load(assetPath); + + // fetch & cache + FormatManager formatManager = FormatManager.Instance; + Map map = formatManager.LoadMap(file.FullName); + foreach (TileSheet t in map.TileSheets) + t.ImageSource = t.ImageSource.Replace(".png", ""); + this.ContentManager.Inject(assetPath, map); + return (T)(object)map; + case ".png": // validate if (typeof(T) != typeof(Texture2D)) @@ -91,6 +111,10 @@ namespace StardewModdingAPI.Framework this.ContentManager.Inject(assetPath, texture); return (T)(object)texture; } + + + + default: throw new ContentLoadException($"Unknown file extension '{file.Extension}'; must be '.xnb' or '.png'."); -- cgit From 24e214b6012de06246deadfd3d63f4a5e25a71ba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 25 May 2017 20:55:08 -0400 Subject: minor cleanup --- src/StardewModdingAPI/Framework/ContentHelper.cs | 25 ++++++++++-------------- src/StardewModdingAPI/Program.cs | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index 5a2a8b9b..10240cd1 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -71,38 +71,37 @@ namespace StardewModdingAPI.Framework // get asset path string assetPath = this.GetModAssetPath(key, file.FullName); + // try cache + if (this.ContentManager.IsLoaded(assetPath)) + return this.ContentManager.Load(assetPath); + // load content switch (file.Extension.ToLower()) { + // XNB file case ".xnb": return this.ContentManager.Load(assetPath); + // unpacked map case ".tbin": // validate if (typeof(T) != typeof(Map)) throw new ContentLoadException($"Can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); - - // try cache - if (this.ContentManager.IsLoaded(assetPath)) - return this.ContentManager.Load(assetPath); // fetch & cache FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); - foreach (TileSheet t in map.TileSheets) - t.ImageSource = t.ImageSource.Replace(".png", ""); + foreach (TileSheet tilesheet in map.TileSheets) + tilesheet.ImageSource = tilesheet.ImageSource.Replace(".png", ""); this.ContentManager.Inject(assetPath, map); return (T)(object)map; - + + // unpacked image case ".png": // validate if (typeof(T) != typeof(Texture2D)) throw new ContentLoadException($"Can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); - // try cache - if (this.ContentManager.IsLoaded(assetPath)) - return this.ContentManager.Load(assetPath); - // fetch & cache using (FileStream stream = File.OpenRead(file.FullName)) { @@ -111,10 +110,6 @@ namespace StardewModdingAPI.Framework this.ContentManager.Inject(assetPath, texture); return (T)(object)texture; } - - - - default: throw new ContentLoadException($"Unknown file extension '{file.Extension}'; must be '.xnb' or '.png'."); diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index b1cfb32d..a22e16a5 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -268,7 +268,6 @@ namespace StardewModdingAPI ** Private methods *********/ /// Assert that the minimum conditions are present to initialise SMAPI without type load exceptions. - /// Returns whether the minimum conditions are met. private static void AssertMinimumCompatibility() { void PrintErrorAndExit(string message) -- cgit From a47ca7e3910d532a3468f2ff222c9c1ed28514c4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 25 May 2017 21:02:05 -0400 Subject: expand .tbin loading to support custom tilesheets from the mod folder --- src/StardewModdingAPI/Framework/ContentHelper.cs | 43 ++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index 10240cd1..e15f0dcf 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -91,8 +91,47 @@ namespace StardewModdingAPI.Framework // fetch & cache FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); - foreach (TileSheet tilesheet in map.TileSheets) - tilesheet.ImageSource = tilesheet.ImageSource.Replace(".png", ""); + if (map.TileSheets.Any()) + { + string relativeMapFolder = Path.GetDirectoryName(key) ?? ""; // folder path containing the map, relative to the mod folder + foreach (TileSheet tilesheet in map.TileSheets) + { + // check for tilesheet relative to map + { + string localKey = Path.Combine(relativeMapFolder, tilesheet.ImageSource); + FileInfo localFile = this.GetModFile(localKey); + if (localFile.Exists) + { + try + { + this.Load(localKey); + } + catch (Exception ex) + { + throw new ContentLoadException($"{this.ModName} failed loading map '{key}' from {source} because the local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); + } + tilesheet.ImageSource = this.GetActualAssetKey(localKey); + continue; + } + } + + // fallback to game content + string contentKey = tilesheet.ImageSource; + if (contentKey.EndsWith(".png")) + contentKey = contentKey.Substring(0, contentKey.Length - 4); + try + { + this.ContentManager.Load(contentKey); + } + catch (Exception ex) + { + throw new ContentLoadException($"{this.ModName} failed loading map '{key}' from {source} because the '{tilesheet.ImageSource}' tilesheet couldn't be found relative to either map file or the game's content folder.", ex); + } + tilesheet.ImageSource = contentKey; + } + } + + // inject map this.ContentManager.Inject(assetPath, map); return (T)(object)map; -- cgit From 7f210cd7b0717cd0ca242ceb730846847a356796 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 25 May 2017 21:35:43 -0400 Subject: fix tilesheets for local XNB maps too --- src/StardewModdingAPI/Framework/ContentHelper.cs | 116 +++++++++++++---------- 1 file changed, 67 insertions(+), 49 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index e15f0dcf..2a590609 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -80,61 +80,30 @@ namespace StardewModdingAPI.Framework { // XNB file case ".xnb": - return this.ContentManager.Load(assetPath); + { + T asset = this.ContentManager.Load(assetPath); + if (asset is Map) + this.FixLocalMapTilesheets(asset as Map, key); + return asset; + } // unpacked map case ".tbin": - // validate - if (typeof(T) != typeof(Map)) - throw new ContentLoadException($"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); - if (map.TileSheets.Any()) { - string relativeMapFolder = Path.GetDirectoryName(key) ?? ""; // folder path containing the map, relative to the mod folder - foreach (TileSheet tilesheet in map.TileSheets) - { - // check for tilesheet relative to map - { - string localKey = Path.Combine(relativeMapFolder, tilesheet.ImageSource); - FileInfo localFile = this.GetModFile(localKey); - if (localFile.Exists) - { - try - { - this.Load(localKey); - } - catch (Exception ex) - { - throw new ContentLoadException($"{this.ModName} failed loading map '{key}' from {source} because the local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); - } - tilesheet.ImageSource = this.GetActualAssetKey(localKey); - continue; - } - } - - // fallback to game content - string contentKey = tilesheet.ImageSource; - if (contentKey.EndsWith(".png")) - contentKey = contentKey.Substring(0, contentKey.Length - 4); - try - { - this.ContentManager.Load(contentKey); - } - catch (Exception ex) - { - throw new ContentLoadException($"{this.ModName} failed loading map '{key}' from {source} because the '{tilesheet.ImageSource}' tilesheet couldn't be found relative to either map file or the game's content folder.", ex); - } - tilesheet.ImageSource = contentKey; - } + // validate + if (typeof(T) != typeof(Map)) + throw new ContentLoadException($"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.FixLocalMapTilesheets(map, key); + + // inject map + this.ContentManager.Inject(assetPath, map); + return (T)(object)map; } - // inject map - this.ContentManager.Inject(assetPath, map); - return (T)(object)map; - // unpacked image case ".png": // validate @@ -188,6 +157,55 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ + /// Fix the tilesheets for a map loaded from the mod folder. + /// The map whose tilesheets to fix. + /// The map asset key within the mod folder. + /// The map tilesheets could not be loaded. + private void FixLocalMapTilesheets(Map map, string mapKey) + { + if (!map.TileSheets.Any()) + return; + + string relativeMapFolder = Path.GetDirectoryName(mapKey) ?? ""; // folder path containing the map, relative to the mod folder + foreach (TileSheet tilesheet in map.TileSheets) + { + // check for tilesheet relative to map + { + string localKey = Path.Combine(relativeMapFolder, tilesheet.ImageSource); + FileInfo localFile = this.GetModFile(localKey); + if (localFile.Exists) + { + try + { + this.Load(localKey); + } + catch (Exception ex) + { + throw new ContentLoadException($"{this.ModName} failed loading map '{mapKey}' from {ContentSource.ModFolder} because the local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); + } + tilesheet.ImageSource = this.GetActualAssetKey(localKey); + continue; + } + } + + // fallback to game content + { + string contentKey = tilesheet.ImageSource; + if (contentKey.EndsWith(".png")) + contentKey = contentKey.Substring(0, contentKey.Length - 4); + try + { + this.ContentManager.Load(contentKey); + } + catch (Exception ex) + { + throw new ContentLoadException($"{this.ModName} failed loading map '{mapKey}' from {ContentSource.ModFolder} because the '{tilesheet.ImageSource}' tilesheet couldn't be found relative to either map file or the game's content folder.", ex); + } + tilesheet.ImageSource = contentKey; + } + } + } + /// Assert that the given key has a valid format. /// The asset key to check. /// The asset key is empty or contains invalid characters. -- cgit From 569ae2b87b10bcef1088b51a33a3f3d5734d60df Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 25 May 2017 21:52:15 -0400 Subject: reduce ContentLoadException nesting --- src/StardewModdingAPI/Framework/ContentHelper.cs | 21 ++++++++++++--------- .../Framework/Exceptions/SContentLoadException.cs | 18 ++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Exceptions/SContentLoadException.cs (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index 2a590609..14b6aa8f 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Exceptions; using StardewValley; using xTile; using xTile.Format; @@ -54,6 +55,8 @@ namespace StardewModdingAPI.Framework /// The content asset couldn't be loaded (e.g. because it doesn't exist). public T Load(string key, ContentSource source = ContentSource.ModFolder) { + SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: {reasonPhrase}."); + this.AssertValidAssetKeyFormat(key); try { @@ -66,7 +69,7 @@ namespace StardewModdingAPI.Framework // get file FileInfo file = this.GetModFile(key); if (!file.Exists) - throw new ContentLoadException($"There is no file at path '{file.FullName}'."); + throw GetContentError($"there's no matching file at path '{file.FullName}'."); // get asset path string assetPath = this.GetModAssetPath(key, file.FullName); @@ -92,7 +95,7 @@ namespace StardewModdingAPI.Framework { // validate if (typeof(T) != typeof(Map)) - throw new ContentLoadException($"Can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{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; @@ -108,7 +111,7 @@ namespace StardewModdingAPI.Framework case ".png": // validate if (typeof(T) != typeof(Texture2D)) - throw new ContentLoadException($"Can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); + throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); // fetch & cache using (FileStream stream = File.OpenRead(file.FullName)) @@ -120,16 +123,16 @@ namespace StardewModdingAPI.Framework } default: - throw new ContentLoadException($"Unknown file extension '{file.Extension}'; must be '.xnb' or '.png'."); + throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'."); } default: - throw new NotSupportedException($"Unknown content source '{source}'."); + throw GetContentError($"unknown content source '{source}'."); } } - catch (Exception ex) + catch (Exception ex) when (!(ex is SContentLoadException)) { - throw new ContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}.", ex); + throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}.", ex); } } @@ -181,7 +184,7 @@ namespace StardewModdingAPI.Framework } catch (Exception ex) { - throw new ContentLoadException($"{this.ModName} failed loading map '{mapKey}' from {ContentSource.ModFolder} because the local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); + throw new ContentLoadException($"The local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); } tilesheet.ImageSource = this.GetActualAssetKey(localKey); continue; @@ -199,7 +202,7 @@ namespace StardewModdingAPI.Framework } catch (Exception ex) { - throw new ContentLoadException($"{this.ModName} failed loading map '{mapKey}' from {ContentSource.ModFolder} because the '{tilesheet.ImageSource}' tilesheet couldn't be found relative to either map file or the game's content folder.", ex); + throw new ContentLoadException($"The '{tilesheet.ImageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); } tilesheet.ImageSource = contentKey; } diff --git a/src/StardewModdingAPI/Framework/Exceptions/SContentLoadException.cs b/src/StardewModdingAPI/Framework/Exceptions/SContentLoadException.cs new file mode 100644 index 00000000..85d85e3d --- /dev/null +++ b/src/StardewModdingAPI/Framework/Exceptions/SContentLoadException.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Xna.Framework.Content; + +namespace StardewModdingAPI.Framework.Exceptions +{ + /// An implementation of used by SMAPI to detect whether it was thrown by SMAPI or the underlying framework. + internal class SContentLoadException : ContentLoadException + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The error message. + /// The underlying exception, if any. + public SContentLoadException(string message, Exception ex = null) + : base(message, ex) { } + } +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 31b2c1cf..ae454a35 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -94,6 +94,7 @@ + -- cgit From 55fa8198ffc140237e1041056f3a4d8f4e7469c8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 27 May 2017 01:01:45 -0400 Subject: fix content API not matching XNB files with two dots (like 'a.b.xnb') if extension isn't specified --- release-notes.md | 1 + src/StardewModdingAPI/Framework/ContentHelper.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ContentHelper.cs') diff --git a/release-notes.md b/release-notes.md index deab5cfe..622fcd49 100644 --- a/release-notes.md +++ b/release-notes.md @@ -31,6 +31,7 @@ For modders: _When loading a map from the mod folder, SMAPI will automatically use tilesheets relative to the map file if they exists. Otherwise it will default to tilesheets in the game content._ * Added `Context.IsInDrawLoop` for specialised mods. * Fixed `smapi-crash.txt` being copied from the default log even if a different path is specified with `--log-path`. +* Fixed the content API not matching XNB filenames with two dots (like `a.b.xnb`) if you don't specify the `.xnb` extension. ## 1.13.1 See [log](https://github.com/Pathoschild/SMAPI/compare/1.13...1.13.1). diff --git a/src/StardewModdingAPI/Framework/ContentHelper.cs b/src/StardewModdingAPI/Framework/ContentHelper.cs index 14b6aa8f..7fd5e803 100644 --- a/src/StardewModdingAPI/Framework/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ContentHelper.cs @@ -225,10 +225,18 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the mod folder. private FileInfo GetModFile(string path) { + // try exact match path = Path.Combine(this.ModFolderPath, this.ContentManager.NormalisePathSeparators(path)); FileInfo file = new FileInfo(path); - if (!file.Exists && file.Extension == "") - file = new FileInfo(Path.Combine(this.ModFolderPath, path + ".xnb")); + + // try with default extension + if (!file.Exists && file.Extension.ToLower() != ".xnb") + { + FileInfo result = new FileInfo(path + ".xnb"); + if (result.Exists) + file = result; + } + return file; } -- cgit