SMAPI - Stardew Modding API

SMAPI - Stardew Modding API

971k Downloads

Investigate tilesheet corruption

Pathoschild opened this issue · 12 comments

commented

Some players report intermittent tilesheet corruption:

commented

If you're affected, please reply with the following info. (Feel free to skip any questions you don't know.)

  • A screenshot of the issue (including the date/clock if possible).
  • Your latest SMAPI log after it happens.
  • Does it only happen on the farm?
  • If you restart the game and load the same save, does it still happen?
  • Do you use any XNB mods (not counting via XNB Loader)?
  • A zip of your save files and mods folder.
commented

@InsaneJ Can you attach a zip of your save files and Mods folder?

commented

I saw this happen on my farm yesterday the moment I got out of bed and went outside. It looked very much like the screenshot above. Unfortunately I don't have the log file for it. Reloading the safe game resolved the issue for me. I didn't venture outside of the farm so I don't know if this happens elsewhere as well.

This is the mod list I was using:

  • !EntoaroxFramework
  • Automate
  • ChestsAnywhere
  • ConsoleCommands
  • CraftingCounter
  • ExtendedMinecart
  • FarmExpansion
  • GateOpener
  • HorseWhistle
  • LevelExtender
  • LookupAnything
  • PurchasableRecipes
  • QuaintFarmBridge
  • QuestDelay
  • RecatchLegendaryFish
  • RentedTools
  • SaveBackup
  • SB_PotC
  • ScrollToBlank
  • ScytheHarvesting
  • ShroomSpotter
  • SkipIntro
  • SkullCavernElevator
  • StackSplitX
  • StardewValleyMP
  • TractorMod
  • TreeTransplant
  • UI Info Suite
commented

I've started a new game and noticed corruption in town:
stardew valley - tile corruption in town

Log up to the moment I walked into town: https://paste2.org/eGkODDKY
Save up to the day I walked into town: Bman_175631855.zip
Mods folder: Mods.zip

commented

Trying to figure out which mods trigger the tile corruption I've found that UI info suite and CarryChest both trigger the issue. Having either one of them in my mods folder results in the screenshot above.

commented

@InsaneJ Thanks! I can't reproduce the issue with your save and mods. Do you have any mods installed under Content?

commented

@InsaneJ I can reproduce tilesheet corruption on the farm with your save (though it's intermittent), thanks! I'll look into this for SMAPI 2.4.

commented

Observations:

  • It happens intermittently, immediately when the save is loaded, persists until the game is closed (exiting to title has no effect), and affects all maps using the corrupted tilesheet.
  • Only reported outdoors.
  • The corruption is visually inconsistent (compare normal, case 1a, case 1b, case 1c, and half-corrupted animation).
  • When the outdoors tilesheet is corrupted, it consistently affects certain tile indexes (including 150, 151, 152, 153, 175, 179, 181, 200, 201, 202, 205, 207, 226, 227, and 231).
  • The map/tilesheet properties and tilesheet order are unchanged when the issue happens:
    No issue:
       Farm (id=Untitled, Layers=5, display size=(5120, 4160)):
          tilesheet #0: id=Paths, source=paths, sheet size=(4, 8), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=32
          tilesheet #1: id=untitled tile sheet, source=Maps\spring_outdoorsTileSheet, sheet size=(25, 79), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=1975
          tilesheet #2: id=zquaintwoodenbridge, source=..\Mods\QuaintFarmBridge\zpathquaintwoodenbridge_spring.png, sheet size=(2, 12), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=24
    
    Broken:
       Farm (id=Untitled, Layers=5, display size=(5120, 4160)):
          tilesheet #0: id=Paths, source=paths, sheet size=(4, 8), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=32
          tilesheet #1: id=untitled tile sheet, source=Maps\spring_outdoorsTileSheet, sheet size=(25, 79), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=1975
          tilesheet #2: id=zquaintwoodenbridge, source=..\Mods\QuaintFarmBridge\zpathquaintwoodenbridge_spring.png, sheet size=(2, 12), tile size=(16, 16),  margin=(0, 0), spacing=(0, 0), tile count=24
    
commented

Further testing:

  • The corruption almost always occurs in two consistent bands across the tilesheet:

  • In rare cases the corruption appears elsewhere (see case 3, case 4), but always in a full horizontal band.
  • This only occurs when the tilesheet is loaded by the game. I reloaded the tilesheet in-game a hundred times, no corruption.
commented

I ran some tests to track corruption of Maps\spring_outdoorsTileSheet while progressively reducing SMAPI's asset interception:

test case corruption in test loads
normal 00001 00100 00000 00001 ✖
SContentManager.LoadImpl<T> ignores loaders 00000 00111 10011 10010 ✖
SContentManager.LoadImpl<T> ignores loaders + editors 11100 10010 10001 10000 ✖
SContentManager.LoadImpl<T> defers to base.Load for loading 01010 01000 00000 01000 ✖
SContentManager.LoadImpl<T> just calls base.Load 10000 10011 10000 00000 ✖
SContentManager.LoadFor<T> just calls base.Load 00000 10011 11101 01000 ✖
IContentHelper.Load<T> does nothing 00000 00000 10011 10000 ✖
IContentHelper does nothing 01001 00100 10110 00000 ✖
SContentManager does nothing (except passthrough Load) 01001 01100 10110 11110 ✖
content interception feature removed entirely 00000 00000 00000 00000 ✓

So the issue is most likely caused by the content interception or something interacting with it, but the issue occurs even if that content interception does nothing.

In very rare cases (roughly 3 of those 200 tests), I saw errors like this. These suggest a race condition due to parallel code; but if that's the cause, it's unclear why it doesn't happen when content interception is removed.

[23:29:37 TRACE Console.Out] getLoadEnumerator('Bman_175631855')
[23:29:39 ERROR Extended Minecart] Could not patch the Beach due to a unknown error
System.ArgumentException: An item with the same key has already been added.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at Microsoft.Xna.Framework.Content.ContentManager.Load[T](String assetName)
   at StardewValley.LocalizedContentManager.Load[T](String assetName)
   at StardewModdingAPI.Framework.SContentManager.LoadFor[T](String assetName, ContentManager instance) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\SContentManager.cs:line 206
   at StardewModdingAPI.Framework.ContentManagerShim.Load[T](String assetName) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\ContentManagerShim.cs:line 40
   at xTile.Display.XnaDisplayDevice.LoadTileSheet(TileSheet tileSheet)
   at xTile.Map.LoadTileSheets(IDisplayDevice displayDevice)
   at Entoarox.ExtendedMinecart.ExtendedMinecartMod.GameEvents_UpdateTick(Object s, EventArgs e)
[23:29:39 TRACE Console.Out] gameMode was 'loadingMode (6)', set to 'selectGameScreen (9)'.
[23:29:39 ERROR Console.Out] System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at xTile.Map.LoadTileSheets(IDisplayDevice displayDevice)
   at StardewValley.Game1.setGraphicsForSeason()
   at StardewValley.Game1.loadForNewGame(Boolean loadedGame)
   at StardewValley.SaveGame.<>c.<getLoadEnumerator>b__51_1()
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
commented

The issue most likely happens sometime during the async SaveGame.GetLoadEnumerator method. Here's the stack trace when the asset is first loaded by SMAPI (identical with and without corruption):

   at StardewModdingAPI.Framework.SContentManager.<>c__DisplayClass41_0`1.<LoadImpl>b__0()
   at StardewModdingAPI.Framework.SContentManager.WithWriteLock[T](Func`1 action)
   at StardewModdingAPI.Framework.SContentManager.LoadImpl[T](String assetName, ContentManager instance)
   at StardewModdingAPI.Framework.SContentManager.LoadFor[T](String assetName, ContentManager instance)
   at StardewModdingAPI.Framework.ContentManagerShim.Load[T](String assetName)
   at xTile.Display.XnaDisplayDevice.LoadTileSheet(TileSheet tileSheet)
   at xTile.Map.LoadTileSheets(IDisplayDevice displayDevice)
   at StardewValley.Game1.setGraphicsForSeason()
   at StardewValley.Game1.loadForNewGame(Boolean loadedGame)
   at StardewValley.SaveGame.<>c.<getLoadEnumerator>b__51_1()
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
commented

Fixed in develop for the upcoming SMAPI 2.4.

The cause is pretty obvious in retrospect. SMAPI pauses mods when the game is running async code, but it doesn't pause mods while a save file is loading. Loading happens asynchronously, which means mods using update-tick events can run code while the save is loading on a separate thread. In rare cases, a mod will use the GPU just as a tilesheet file is being loaded, corrupting the tilesheet. The reason mods which use the content manager tend to trigger the corruption isn't because of the content manager directly, it's just because they're more likely to use the GPU while a tilesheet is being loaded.

As of SMAPI 2.4, SMAPI will no longer raise events while a load is in progress.