SMAPI - Stardew Modding API

SMAPI - Stardew Modding API

971k Downloads

Can't read unpacked map file '..\Mods\....tbin' directly from the underlying content manager

Pathoschild opened this issue ยท 4 comments

commented

Farm Expansion fails with this error:

[00:46:52 ERROR Farm Expansion] This mod failed in the SaveEvents.AfterLoad event. Technical details:
StardewModdingAPI.Framework.Exceptions.SContentLoadException: Failed loading content asset '..\Mods\FarmExpansion 130\assets\FarmExpansion.tbin': can't read unpacked map file '..\Mods\FarmExpansion 130\assets\FarmExpansion.tbin' directly from the underlying content manager. It must be loaded through the mod's StardewModdingAPI.IModHelper.Content helper.
   at StardewModdingAPI.Framework.SContentManager.Load[T](String assetName, LanguageCode language) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\SContentManager.cs:line 150
   at StardewModdingAPI.Framework.SContentManager.Load[T](String assetName) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\SContentManager.cs:line 111
   at StardewValley.GameLocation.loadMap(String mapPath) in C:\Users\gitlab-runner\gitlab-runner\builds\5c0f9387\0\chucklefish\stardewvalley\Farmer\Farmer\Locations\GameLocation.cs:line 612
   at StardewValley.GameLocation.reloadMap() in C:\Users\gitlab-runner\gitlab-runner\builds\5c0f9387\0\chucklefish\stardewvalley\Farmer\Farmer\Locations\GameLocation.cs:line 666
   at StardewValley.GameLocation..ctor(String mapPath, String name) in C:\Users\gitlab-runner\gitlab-runner\builds\5c0f9387\0\chucklefish\stardewvalley\Farmer\Farmer\Locations\GameLocation.cs:line 583
   at StardewValley.Locations.BuildableGameLocation..ctor(String mapPath, String name) in C:\Users\gitlab-runner\gitlab-runner\builds\5c0f9387\0\chucklefish\stardewvalley\Farmer\Farmer\Locations\BuildableGameLocation.cs:line 24
   at StardewValley.Farm..ctor(String mapPath, String name) in C:\Users\gitlab-runner\gitlab-runner\builds\5c0f9387\0\chucklefish\stardewvalley\Farmer\Farmer\Locations\Farm.cs:line 55
   at FarmExpansion.FarmExpansion..ctor(String mapAssetKey, String name, FEFramework framework)
   at FarmExpansion.Framework.FEFramework.SaveEvents_AfterLoad(Object sender, EventArgs e)
   at StardewModdingAPI.Framework.Events.ManagedEvent.Raise() in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\Events\ManagedEvent.cs:line 110

See full log.

commented

I spent a bit of time today attempting to get Farm Expansion to work with the latest beta version of SMAPI, and stumbled across this issue. I did some digging, and I thought I would share the results of my analysis. I am fairly new to modding/open source in general, and have never looked at this codebase before today, so probably take the following with a grain of salt - I just wanted to write out my findings.

  1. The StardewValley.Farm constructor changed at some point from taking a Map object to taking a map path string as its first argument.

  2. #488 overhauled SMAPI's content caching system, making it entirely decentralized. From what I can tell, this means that a cache for a mod's content manager will be entirely separate from a cache for the game's content manager.

  3. because of 1), the game has to load the map itself - it cannot be pre-loaded by the mod. Even if the mod loads the map through its content manager before calling the Farm constructor and that manager caches it, because of 2) the game will not find it in the cache, causing it to go through SContentManager.Load, get a cache miss, and proceed to the throw condition for .tbin files.

While I was looking through #488, I saw @Entoarox's suggestion of a centralized cache that is then copied into per-manager caches - I think that might work here.

commented

Thanks for the help! I'm working on an approach which keeps the decentralised cache (so we don't have an exponential number of cloned assets), but allows cloned asset transfers between content managers. This approach also eliminates the ambiguity between Content and mod assets.

The essential change is that each mod asset now has a namespaced key handled by SMAPI (instead of a file path handled by the XNA content pipeline). When a content manager receives a namespaced key, it checks whether it owns the namespace. If so, it loads the asset from its root folder. If not, it requests the asset through the content coordinator and caches its own copy. (The content coordinator is an existing class which handles any central content logic, like content manager creation or cache invalidation.)

For example, here's how that works in practice:

  1. Farm Expansion requests assets\map.tbin from its mod folder.
  2. The Farm Expansion content manager caches SMAPI\advize.farmexpansion\assets\map.tbin locally.
  3. The game later requests SMAPI\advize.farmexpansion\assets\map.tbin from Game1.content.
  4. Game1.content detects the namespaced key, realises that it doesn't have a cached copy and doesn't own the namespace, and requests it from the content coordinator.
  5. The content coordinator requests it from the Farm Expansion content manager, which returns the cached copy (or reloads it if needed, e.g. due to cache invalidation).
  6. The content coordinator clones the asset (if needed), and returns it to Game1.content.
  7. Game1.content caches SMAPI\advize.farmexpansion\assets\map.tbin and returns it to the game.

If the game requests it again, Game1.content can immediately return its own cached copy. Any asset changes will only affect the content manager it was taken from, and cache invalidation is easy since all content managers use the same asset key.

This still needs more polishing and testing before it's ready to commit, but I think it combines the benefits of a decentralised cache (e.g. changes aren't leaked between content managers) and a centralised one (e.g. less memory overhead).

commented

That sounds like an excellent solution - thanks for taking the time to write out that explanation!

commented

Fixed in develop for the upcoming SMAPI 2.6 beta.