summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj6
-rw-r--r--src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/ModEntry.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs79
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj3
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/Core/InterfaceProxyTests.cs7
-rw-r--r--src/SMAPI.Tests/SMAPI.Tests.csproj10
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj4
-rw-r--r--src/SMAPI.Web/SMAPI.Web.csproj14
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json5
-rw-r--r--src/SMAPI.sln4
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForImage.cs173
-rw-r--r--src/SMAPI/Framework/Content/ContentCache.cs33
-rw-r--r--src/SMAPI/Framework/Content/RawTextureData.cs10
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs16
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs168
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs19
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs34
-rw-r--r--src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs118
-rw-r--r--src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs57
-rw-r--r--src/SMAPI/Framework/SCore.cs29
-rw-r--r--src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs25
-rw-r--r--src/SMAPI/IAssetDataForImage.cs10
-rw-r--r--src/SMAPI/IContentHelper.cs2
-rw-r--r--src/SMAPI/IContentPack.cs2
-rw-r--r--src/SMAPI/IModContentHelper.cs2
-rw-r--r--src/SMAPI/IRawTextureData.cs17
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs26
-rw-r--r--src/SMAPI/Properties/AssemblyInfo.cs1
-rw-r--r--src/SMAPI/SMAPI.config.json18
-rw-r--r--src/SMAPI/SMAPI.csproj7
36 files changed, 531 insertions, 395 deletions
diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
index 264932e4..3be9c225 100644
--- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
+++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj
@@ -6,9 +6,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
- <PackageReference Include="NUnit" Version="3.13.2" />
- <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
+ <PackageReference Include="NUnit" Version="3.13.3" />
+ <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
index c5790186..e25da168 100644
--- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
+++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10" />
- <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!--
This is imported through Microsoft.Build.Utilities.Core. When installed by a mod, NuGet
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 564e480e..300de9d2 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.14.7",
+ "Version": "3.15.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.14.7"
+ "MinimumApiVersion": "3.15.0"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index bfbfd2dc..22e68421 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -2,6 +2,7 @@ using System;
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Internal.Patching;
+using StardewModdingAPI.Mods.ErrorHandler.ModPatches;
using StardewModdingAPI.Mods.ErrorHandler.Patches;
using StardewValley;
@@ -29,6 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler
// apply patches
HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor,
+ // game patches
new DialoguePatcher(monitorForGame, this.Helper.Reflection),
new EventPatcher(monitorForGame),
new GameLocationPatcher(monitorForGame),
@@ -37,7 +39,10 @@ namespace StardewModdingAPI.Mods.ErrorHandler
new ObjectPatcher(),
new SaveGamePatcher(this.Monitor, this.OnSaveContentRemoved),
new SpriteBatchPatcher(),
- new UtilityPatcher()
+ new UtilityPatcher(),
+
+ // mod patches
+ new PyTkPatcher(helper.ModRegistry)
);
// hook events
diff --git a/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs b/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs
new file mode 100644
index 00000000..9ee864db
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using HarmonyLib;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using StardewModdingAPI.Framework;
+using StardewModdingAPI.Framework.Content;
+using StardewModdingAPI.Internal;
+using StardewModdingAPI.Internal.Patching;
+
+//
+// This is part of a three-part fix for PyTK 1.23.0 and earlier. When removing this, search
+// 'Platonymous.Toolkit' to find the other part in SMAPI and Content Patcher.
+//
+
+namespace StardewModdingAPI.Mods.ErrorHandler.ModPatches
+{
+ /// <summary>Harmony patches for the PyTK mod for compatibility with newer SMAPI versions.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "'Platonymous' is part of the mod ID.")]
+ internal class PyTkPatcher : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The PyTK mod metadata, if it's installed.</summary>
+ private static IModMetadata? PyTk;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="modRegistry">The mod registry from which to read PyTK metadata.</param>
+ public PyTkPatcher(IModRegistry modRegistry)
+ {
+ IModMetadata? pyTk = (IModMetadata?)modRegistry.Get(@"Platonymous.Toolkit");
+ if (pyTk is not null && !pyTk.Manifest.Version.IsNewerThan("1.23.0"))
+ PyTkPatcher.PyTk = pyTk;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ try
+ {
+ // get mod info
+ IModMetadata? pyTk = PyTkPatcher.PyTk;
+ if (pyTk is null)
+ return;
+
+ // get patch method
+ const string patchMethodName = "PatchImage";
+ MethodInfo? patch = AccessTools.Method(pyTk.Mod!.GetType(), patchMethodName);
+ if (patch is null)
+ {
+ monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
+ monitor.Log($"Couldn't find patch method '{pyTk.Mod.GetType().FullName}.{patchMethodName}'.");
+ return;
+ }
+
+ // apply patch
+ harmony = new($"{harmony.Id}.compatibility-patches.PyTK");
+ harmony.Patch(
+ original: AccessTools.Method(typeof(AssetDataForImage), nameof(AssetDataForImage.PatchImage), new[] { typeof(Texture2D), typeof(Rectangle), typeof(Rectangle), typeof(PatchMode) }),
+ prefix: new HarmonyMethod(patch)
+ );
+ }
+ catch (Exception ex)
+ {
+ monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
+ monitor.Log(ex.GetLogSummary());
+ }
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
index 78cdb315..53c37e97 100644
--- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
+++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
@@ -24,7 +24,4 @@
<None Update="i18n\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
-
- <Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
- <Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
</Project>
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index 39d22b5f..15a1e0f3 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.14.7",
+ "Version": "3.15.0",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.14.7"
+ "MinimumApiVersion": "3.15.0"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 8eaf2475..1a11742c 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.14.7",
+ "Version": "3.15.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.14.7"
+ "MinimumApiVersion": "3.15.0"
}
diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
index 6be97526..d14c116f 100644
--- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
+++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
@@ -29,11 +29,8 @@ namespace SMAPI.Tests.Core
/// <summary>The random number generator with which to create sample values.</summary>
private readonly Random Random = new();
- /// <summary>Sample user inputs for season names.</summary>
- private static readonly IInterfaceProxyFactory[] ProxyFactories = {
- new InterfaceProxyFactory(),
- new OriginalInterfaceProxyFactory()
- };
+ /// <summary>The proxy factory to use in unit tests.</summary>
+ private static readonly IInterfaceProxyFactory[] ProxyFactories = { new InterfaceProxyFactory() };
/*********
diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj
index 67997b30..2c32a932 100644
--- a/src/SMAPI.Tests/SMAPI.Tests.csproj
+++ b/src/SMAPI.Tests/SMAPI.Tests.csproj
@@ -14,11 +14,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="FluentAssertions" Version="6.5.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
- <PackageReference Include="Moq" Version="4.16.1" />
- <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
- <PackageReference Include="NUnit" Version="3.13.2" />
+ <PackageReference Include="FluentAssertions" Version="6.7.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
+ <PackageReference Include="Moq" Version="4.18.1" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
+ <PackageReference Include="NUnit" Version="3.13.3" />
</ItemGroup>
<ItemGroup>
diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
index ec27bf79..e021993f 100644
--- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
+++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
@@ -9,8 +9,8 @@
<Import Project="..\..\build\common.targets" />
<ItemGroup>
- <PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
- <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ <PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj
index f1400e62..4c2569e1 100644
--- a/src/SMAPI.Web/SMAPI.Web.csproj
+++ b/src/SMAPI.Web/SMAPI.Web.csproj
@@ -15,14 +15,14 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
- <PackageReference Include="Hangfire.AspNetCore" Version="1.7.27" />
+ <PackageReference Include="Azure.Storage.Blobs" Version="12.12.0" />
+ <PackageReference Include="Hangfire.AspNetCore" Version="1.7.29" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
- <PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
- <PackageReference Include="Humanizer.Core" Version="2.13.14" />
- <PackageReference Include="JetBrains.Annotations" Version="2021.3.0" />
- <PackageReference Include="Markdig" Version="0.26.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
+ <PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
+ <PackageReference Include="Humanizer.Core" Version="2.14.1" />
+ <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
+ <PackageReference Include="Markdig" Version="0.30.2" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.5" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
index 53c9db82..16a89647 100644
--- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
+++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
@@ -170,6 +170,11 @@
/*********
** Broke in SMAPI 3.14.0
*********/
+ "CFAutomate": {
+ "ID": "Platonymous.CFAutomate",
+ "~2.12.9 | Status": "AssumeBroken",
+ "~2.12.9 | StatusReasonDetails": "causes runtime errors in newer versions of Automate"
+ },
"Dynamic Game Assets": {
"ID": "spacechase0.DynamicGameAssets",
"~1.4.1 | Status": "AssumeBroken",
diff --git a/src/SMAPI.sln b/src/SMAPI.sln
index d9f60a5c..2e5ba0ea 100644
--- a/src/SMAPI.sln
+++ b/src/SMAPI.sln
@@ -103,14 +103,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{3B5BF14D-F61
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiProvider", "SMAPI.Tests.ModApiProvider\SMAPI.Tests.ModApiProvider.csproj", "{239AEEAC-07D1-4A3F-AA99-8C74F5038F50}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI.Tests.ModApiConsumer", "SMAPI.Tests.ModApiConsumer\SMAPI.Tests.ModApiConsumer.csproj", "{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiConsumer", "SMAPI.Tests.ModApiConsumer\SMAPI.Tests.ModApiConsumer.csproj", "{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SMAPI.Internal\SMAPI.Internal.projitems*{0634ea4c-3b8f-42db-aea6-ca9e4ef6e92f}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{0a9bb24f-15ff-4c26-b1a2-81f7ae316518}*SharedItemsImports = 5
- SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5
- SMAPI.Internal\SMAPI.Internal.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5
SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{6c16e948-3e5c-47a7-bf4b-07a7469a87a5}*SharedItemsImports = 13
SMAPI.Internal\SMAPI.Internal.projitems*{80efd92f-728f-41e0-8a5b-9f6f49a91899}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index c63324e3..db88563e 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -50,7 +50,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.14.7";
+ internal static string RawApiVersion = "3.15.0";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs
index 97729c95..3393b22f 100644
--- a/src/SMAPI/Framework/Content/AssetDataForImage.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewValley;
@@ -29,86 +30,154 @@ namespace StardewModdingAPI.Framework.Content
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
/// <inheritdoc />
- public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
+ public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
{
- // get texture
+ this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
+
+ // validate source data
if (source == null)
- throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
- Texture2D target = this.Data;
+ throw new ArgumentNullException(nameof(source), "Can't patch from null source data.");
- // get areas
- sourceArea ??= new Rectangle(0, 0, source.Width, source.Height);
- targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));
+ // get the pixels for the source area
+ Color[] sourceData;
+ {
+ int areaX = sourceArea.Value.X;
+ int areaY = sourceArea.Value.Y;
+ int areaWidth = sourceArea.Value.Width;
+ int areaHeight = sourceArea.Value.Height;
- // validate
+ if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height)
+ sourceData = source.Data;
+ else
+ {
+ sourceData = new Color[areaWidth * areaHeight];
+ int i = 0;
+ for (int y = areaY, maxY = areaY + areaHeight - 1; y <= maxY; y++)
+ {
+ for (int x = areaX, maxX = areaX + areaWidth - 1; x <= maxX; x++)
+ {
+ int targetIndex = (y * source.Width) + x;
+ sourceData[i++] = source.Data[targetIndex];
+ }
+ }
+ }
+ }
+
+ // apply
+ this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
+ }
+
+ /// <inheritdoc />
+ public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
+ {
+ this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
+
+ // validate source texture
+ if (source == null)
+ throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
if (!source.Bounds.Contains(sourceArea.Value))
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
- if (!target.Bounds.Contains(targetArea.Value))
- throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
- if (sourceArea.Value.Size != targetArea.Value.Size)
- throw new InvalidOperationException("The source and target areas must be the same size.");
// get source data
int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;
Color[] sourceData = GC.AllocateUninitializedArray<Color>(pixelCount);
source.GetData(0, sourceArea, sourceData, 0, pixelCount);
- // merge data in overlay mode
+ // apply
+ this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
+ }
+
+ /// <inheritdoc />
+ public bool ExtendImage(int minWidth, int minHeight)
+ {
+ if (this.Data.Width >= minWidth && this.Data.Height >= minHeight)
+ return false;
+
+ Texture2D original = this.Data;
+ Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight));
+ this.ReplaceWith(texture);
+ this.PatchImage(original);
+ return true;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get the bounds for an image patch.</summary>
+ /// <param name="sourceArea">The source area to set if needed.</param>
+ /// <param name="targetArea">The target area to set if needed.</param>
+ /// <param name="sourceWidth">The width of the full source image.</param>
+ /// <param name="sourceHeight">The height of the full source image.</param>
+ private void GetPatchBounds([NotNull] ref Rectangle? sourceArea, [NotNull] ref Rectangle? targetArea, int sourceWidth, int sourceHeight)
+ {
+ sourceArea ??= new Rectangle(0, 0, sourceWidth, sourceHeight);
+ targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, this.Data.Width), Math.Min(sourceArea.Value.Height, this.Data.Height));
+ }
+
+ /// <summary>Overwrite part of the image.</summary>
+ /// <param name="sourceData">The image data to patch into the content.</param>
+ /// <param name="sourceWidth">The pixel width of the source image.</param>
+ /// <param name="sourceHeight">The pixel height of the source image.</param>
+ /// <param name="sourceArea">The part of the <paramref name="sourceData"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="sourceData"/> texture.</param>
+ /// <param name="targetArea">The part of the content to patch (or <c>null</c> to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet.</param>
+ /// <param name="patchMode">Indicates how an image should be patched.</param>
+ /// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetArea"/> is outside the bounds of the spritesheet.</exception>
+ /// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
+ private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeight, Rectangle sourceArea, Rectangle targetArea, PatchMode patchMode)
+ {
+ // get texture
+ Texture2D target = this.Data;
+ int pixelCount = sourceArea.Width * sourceArea.Height;
+
+ // validate
+ if (sourceArea.X < 0 || sourceArea.Y < 0 || sourceArea.Right > sourceWidth || sourceArea.Bottom > sourceHeight)
+ throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
+ if (!target.Bounds.Contains(targetArea))
+ throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
+ if (sourceArea.Size != targetArea.Size)
+ throw new InvalidOperationException("The source and target areas must be the same size.");
+
+ // merge data
if (patchMode == PatchMode.Overlay)
{
// get target data
- Color[] targetData = GC.AllocateUninitializedArray<Color>(pixelCount);
- target.GetData(0, targetArea, targetData, 0, pixelCount);
+ Color[] mergedData = GC.AllocateUninitializedArray<Color>(pixelCount);
+ target.GetData(0, targetArea, mergedData, 0, pixelCount);
// merge pixels
- for (int i = 0; i < sourceData.Length; i++)
+ for (int i = 0; i < pixelCount; i++)
{
Color above = sourceData[i];
- Color below = targetData[i];
+ Color below = mergedData[i];
// shortcut transparency
if (above.A < MinOpacity)
- {
- sourceData[i] = below;
continue;
- }
if (below.A < MinOpacity)
- {
- sourceData[i] = above;
- continue;
- }
+ mergedData[i] = above;
// merge pixels
- // This performs a conventional alpha blend for the pixels, which are already
- // premultiplied by the content pipeline. The formula is derived from
- // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
- // Note: don't use named arguments here since they're different between
- // Linux/macOS and Windows.
- float alphaBelow = 1 - (above.A / 255f);
- sourceData[i] = new Color(
- (int)(above.R + (below.R * alphaBelow)), // r
- (int)(above.G + (below.G * alphaBelow)), // g
- (int)(above.B + (below.B * alphaBelow)), // b
- Math.Max(above.A, below.A) // a
- );
+ else
+ {
+ // This performs a conventional alpha blend for the pixels, which are already
+ // premultiplied by the content pipeline. The formula is derived from
+ // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
+ float alphaBelow = 1 - (above.A / 255f);
+ mergedData[i] = new Color(
+ r: (int)(above.R + (below.R * alphaBelow)),
+ g: (int)(above.G + (below.G * alphaBelow)),
+ b: (int)(above.B + (below.B * alphaBelow)),
+ alpha: Math.Max(above.A, below.A)
+ );
+ }
}
- }
-
- // patch target texture
- target.SetData(0, targetArea, sourceData, 0, pixelCount);
- }
-
- /// <inheritdoc />
- public bool ExtendImage(int minWidth, int minHeight)
- {
- if (this.Data.Width >= minWidth && this.Data.Height >= minHeight)
- return false;
- Texture2D original = this.Data;
- Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight));
- this.ReplaceWith(texture);
- this.PatchImage(original);
- return true;
+ target.SetData(0, targetArea, mergedData, 0, pixelCount);
+ }
+ else
+ target.SetData(0, targetArea, sourceData, 0, pixelCount);
}
}
}
diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs
index 736ee5da..bf42812b 100644
--- a/src/SMAPI/Framework/Content/ContentCache.cs
+++ b/src/SMAPI/Framework/Content/ContentCache.cs
@@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Content
** Fields
*********/
/// <summary>The underlying