Merge branch 'develop' into stable
This commit is contained in:
commit
211f89821e
|
@ -1,7 +1,7 @@
|
||||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--set general build properties -->
|
<!--set general build properties -->
|
||||||
<Version>3.12.2</Version>
|
<Version>3.12.3</Version>
|
||||||
<Product>SMAPI</Product>
|
<Product>SMAPI</Product>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
← [README](README.md)
|
← [README](README.md)
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
|
## 3.12.3
|
||||||
|
Released 25 August 2021 for Stardew Valley 1.5.4 or later.
|
||||||
|
|
||||||
|
* For players:
|
||||||
|
* Added friendly error in 64-bit mode when a mod is 32-bit only.
|
||||||
|
* Fixed console encoding issues on Linux/macOS.
|
||||||
|
* Fixed some installer errors not showing info header.
|
||||||
|
|
||||||
|
* For mod authors:
|
||||||
|
* Added `helper.Translation.GetInAllLocales` to get a translation in every available locale.
|
||||||
|
* Fixed Visual Studio debugger crash when any mods are rewritten for compatibility (thanks to spacechase0!).
|
||||||
|
* Fixed `helper.Data.WriteJsonFile` not deleting the file if the model is null, unlike the other `Write*` methods.
|
||||||
|
* Fixed error-handling for `StackOverflowException` thrown on Linux/macOS.
|
||||||
|
* Internal changes to prepare for Stardew Valley 1.5.5.
|
||||||
|
|
||||||
|
* For the web API:
|
||||||
|
* Fixed update checks not shown for prerelease mod versions when you have a SMAPI beta.
|
||||||
|
* Fixed update checks shown for prerelease mod versions if you have a working non-prerelease version.
|
||||||
|
|
||||||
## 3.12.2
|
## 3.12.2
|
||||||
Released 04 August 2021 for Stardew Valley 1.5.4 or later.
|
Released 05 August 2021 for Stardew Valley 1.5.4 or later.
|
||||||
|
|
||||||
* For players:
|
* For players:
|
||||||
* Fixed error creating a new save or joining a multiplayer world in 3.12.1.
|
* Fixed error creating a new save or joining a multiplayer world in 3.12.1.
|
||||||
|
|
|
@ -365,7 +365,10 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi
|
||||||
when you compile it.
|
when you compile it.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
## 3.3
|
## Upcoming release
|
||||||
|
* Improved analyzer performance by enabling parallel execution.
|
||||||
|
|
||||||
|
## 3.3.0
|
||||||
Released 30 March 2021.
|
Released 30 March 2021.
|
||||||
|
|
||||||
* Added a build warning when the mod isn't compiled for `Any CPU`.
|
* Added a build warning when the mod isn't compiled for `Any CPU`.
|
||||||
|
@ -385,19 +388,19 @@ Released 11 September 2020.
|
||||||
* Added more detailed logging.
|
* Added more detailed logging.
|
||||||
* Fixed _path's format is not supported_ error when using default `Mods` path in 3.2.
|
* Fixed _path's format is not supported_ error when using default `Mods` path in 3.2.
|
||||||
|
|
||||||
### 3.2
|
### 3.2.0
|
||||||
Released 07 September 2020.
|
Released 07 September 2020.
|
||||||
|
|
||||||
* Added option to change `Mods` folder path.
|
* Added option to change `Mods` folder path.
|
||||||
* Rewrote documentation to make it easier to read.
|
* Rewrote documentation to make it easier to read.
|
||||||
|
|
||||||
### 3.1
|
### 3.1.0
|
||||||
Released 01 February 2020.
|
Released 01 February 2020.
|
||||||
|
|
||||||
* Added support for semantic versioning 2.0.
|
* Added support for semantic versioning 2.0.
|
||||||
* `0Harmony.dll` is now ignored if the mod references Harmony directly (it's bundled with SMAPI).
|
* `0Harmony.dll` is now ignored if the mod references Harmony directly (it's bundled with SMAPI).
|
||||||
|
|
||||||
### 3.0
|
### 3.0.0
|
||||||
Released 26 November 2019.
|
Released 26 November 2019.
|
||||||
|
|
||||||
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
|
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
|
||||||
|
@ -412,14 +415,14 @@ Released 26 November 2019.
|
||||||
* Dropped support for older versions of SMAPI and Visual Studio.
|
* Dropped support for older versions of SMAPI and Visual Studio.
|
||||||
* Migrated package icon to NuGet's new format.
|
* Migrated package icon to NuGet's new format.
|
||||||
|
|
||||||
### 2.2
|
### 2.2.0
|
||||||
Released 28 October 2018.
|
Released 28 October 2018.
|
||||||
|
|
||||||
* Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
* Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
||||||
* Added default game paths for 32-bit Windows.
|
* Added default game paths for 32-bit Windows.
|
||||||
* Fixed valid manifests marked invalid in some cases.
|
* Fixed valid manifests marked invalid in some cases.
|
||||||
|
|
||||||
### 2.1
|
### 2.1.0
|
||||||
Released 27 July 2018.
|
Released 27 July 2018.
|
||||||
|
|
||||||
* Added support for Stardew Valley 1.3.
|
* Added support for Stardew Valley 1.3.
|
||||||
|
@ -439,7 +442,7 @@ Released 11 October 2017.
|
||||||
|
|
||||||
* Fixed mod deploy failing to create subfolders if they don't already exist.
|
* Fixed mod deploy failing to create subfolders if they don't already exist.
|
||||||
|
|
||||||
### 2.0
|
### 2.0.0
|
||||||
Released 11 October 2017.
|
Released 11 October 2017.
|
||||||
|
|
||||||
* Added: mods are now copied into the `Mods` folder automatically (configurable).
|
* Added: mods are now copied into the `Mods` folder automatically (configurable).
|
||||||
|
@ -457,7 +460,7 @@ Released 28 July 2017.
|
||||||
* The manifest/i18n files in the project now take precedence over those in the build output if both
|
* The manifest/i18n files in the project now take precedence over those in the build output if both
|
||||||
are present.
|
are present.
|
||||||
|
|
||||||
### 1.7
|
### 1.7.0
|
||||||
Released 28 July 2017.
|
Released 28 July 2017.
|
||||||
|
|
||||||
* Added option to create release zips on build.
|
* Added option to create release zips on build.
|
||||||
|
@ -474,19 +477,19 @@ Released 09 July 2017.
|
||||||
|
|
||||||
* Improved crossplatform game path detection.
|
* Improved crossplatform game path detection.
|
||||||
|
|
||||||
### 1.6
|
### 1.6.0
|
||||||
Released 05 June 2017.
|
Released 05 June 2017.
|
||||||
|
|
||||||
* Added support for deploying mod files into `Mods` automatically.
|
* Added support for deploying mod files into `Mods` automatically.
|
||||||
* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI.
|
* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI.
|
||||||
|
|
||||||
### 1.5
|
### 1.5.0
|
||||||
Released 23 January 2017.
|
Released 23 January 2017.
|
||||||
|
|
||||||
* Added support for setting a custom game path globally.
|
* Added support for setting a custom game path globally.
|
||||||
* Added default GOG path on macOS.
|
* Added default GOG path on macOS.
|
||||||
|
|
||||||
### 1.4
|
### 1.4.0
|
||||||
Released 11 January 2017.
|
Released 11 January 2017.
|
||||||
|
|
||||||
* Fixed detection of non-default game paths on 32-bit Windows.
|
* Fixed detection of non-default game paths on 32-bit Windows.
|
||||||
|
@ -494,22 +497,22 @@ Released 11 January 2017.
|
||||||
* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms
|
* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms
|
||||||
mods automatically).
|
mods automatically).
|
||||||
|
|
||||||
### 1.3
|
### 1.3.0
|
||||||
Released 31 December 2016.
|
Released 31 December 2016.
|
||||||
|
|
||||||
* Added support for non-default game paths on Windows.
|
* Added support for non-default game paths on Windows.
|
||||||
|
|
||||||
### 1.2
|
### 1.2.0
|
||||||
Released 24 October 2016.
|
Released 24 October 2016.
|
||||||
|
|
||||||
* Exclude game binaries from mod build output.
|
* Exclude game binaries from mod build output.
|
||||||
|
|
||||||
### 1.1
|
### 1.1.0
|
||||||
Released 21 October 2016.
|
Released 21 October 2016.
|
||||||
|
|
||||||
* Added support for overriding the target platform.
|
* Added support for overriding the target platform.
|
||||||
|
|
||||||
### 1.0
|
### 1.0.0
|
||||||
Released 21 October 2016.
|
Released 21 October 2016.
|
||||||
|
|
||||||
* Initial release.
|
* Initial release.
|
||||||
|
|
|
@ -272,7 +272,6 @@ namespace StardewModdingApi.Installer
|
||||||
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
|
DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath);
|
||||||
paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName);
|
paths = new InstallerPaths(bundleDir, installDir, context.ExecutableName);
|
||||||
}
|
}
|
||||||
Console.Clear();
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -309,6 +308,7 @@ namespace StardewModdingApi.Installer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
|
|
@ -13,19 +13,26 @@ namespace StardewModdingAPI.Internal
|
||||||
/// <param name="exception">The error to summarize.</param>
|
/// <param name="exception">The error to summarize.</param>
|
||||||
public static string GetLogSummary(this Exception exception)
|
public static string GetLogSummary(this Exception exception)
|
||||||
{
|
{
|
||||||
switch (exception)
|
try
|
||||||
{
|
{
|
||||||
case TypeLoadException ex:
|
switch (exception)
|
||||||
return $"Failed loading type '{ex.TypeName}': {exception}";
|
{
|
||||||
|
case TypeLoadException ex:
|
||||||
|
return $"Failed loading type '{ex.TypeName}': {exception}";
|
||||||
|
|
||||||
case ReflectionTypeLoadException ex:
|
case ReflectionTypeLoadException ex:
|
||||||
string summary = ex.ToString();
|
string summary = ex.ToString();
|
||||||
foreach (Exception childEx in ex.LoaderExceptions ?? new Exception[0])
|
foreach (Exception childEx in ex.LoaderExceptions ?? new Exception[0])
|
||||||
summary += $"\n\n{childEx?.GetLogSummary()}";
|
summary += $"\n\n{childEx?.GetLogSummary()}";
|
||||||
return summary;
|
return summary;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return exception.ToString();
|
return exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,6 +174,9 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
|
||||||
context.RegisterSyntaxNodeAction(
|
context.RegisterSyntaxNodeAction(
|
||||||
this.AnalyzeMemberAccess,
|
this.AnalyzeMemberAccess,
|
||||||
SyntaxKind.SimpleMemberAccessExpression,
|
SyntaxKind.SimpleMemberAccessExpression,
|
||||||
|
|
|
@ -56,6 +56,9 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
public override void Initialize(AnalysisContext context)
|
public override void Initialize(AnalysisContext context)
|
||||||
{
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
|
||||||
context.RegisterSyntaxNodeAction(
|
context.RegisterSyntaxNodeAction(
|
||||||
this.AnalyzeObsoleteFields,
|
this.AnalyzeObsoleteFields,
|
||||||
SyntaxKind.SimpleMemberAccessExpression,
|
SyntaxKind.SimpleMemberAccessExpression,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Console Commands",
|
"Name": "Console Commands",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.12.2",
|
"Version": "3.12.3",
|
||||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||||
"UniqueID": "SMAPI.ConsoleCommands",
|
"UniqueID": "SMAPI.ConsoleCommands",
|
||||||
"EntryDll": "ConsoleCommands.dll",
|
"EntryDll": "ConsoleCommands.dll",
|
||||||
"MinimumApiVersion": "3.12.2"
|
"MinimumApiVersion": "3.12.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Error Handler",
|
"Name": "Error Handler",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.12.2",
|
"Version": "3.12.3",
|
||||||
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
|
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
|
||||||
"UniqueID": "SMAPI.ErrorHandler",
|
"UniqueID": "SMAPI.ErrorHandler",
|
||||||
"EntryDll": "ErrorHandler.dll",
|
"EntryDll": "ErrorHandler.dll",
|
||||||
"MinimumApiVersion": "3.12.2"
|
"MinimumApiVersion": "3.12.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Save Backup",
|
"Name": "Save Backup",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.12.2",
|
"Version": "3.12.3",
|
||||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||||
"UniqueID": "SMAPI.SaveBackup",
|
"UniqueID": "SMAPI.SaveBackup",
|
||||||
"EntryDll": "SaveBackup.dll",
|
"EntryDll": "SaveBackup.dll",
|
||||||
"MinimumApiVersion": "3.12.2"
|
"MinimumApiVersion": "3.12.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,10 +90,10 @@ namespace StardewModdingAPI.Toolkit.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get whether an executable is 64-bit.</summary>
|
/// <summary>Get whether an executable is 64-bit.</summary>
|
||||||
/// <param name="executablePath">The absolute path to the executable file.</param>
|
/// <param name="path">The absolute path to the assembly file.</param>
|
||||||
public static bool Is64BitAssembly(string executablePath)
|
public static bool Is64BitAssembly(string path)
|
||||||
{
|
{
|
||||||
return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86;
|
return AssemblyName.GetAssemblyName(path).ProcessorArchitecture != ProcessorArchitecture.X86;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Toolkit
|
||||||
|
{
|
||||||
|
/// <summary>A comparer for semantic versions based on the <see cref="SemanticVersion.CompareTo(ISemanticVersion)"/> field.</summary>
|
||||||
|
public class SemanticVersionComparer : IComparer<ISemanticVersion>
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>A singleton instance of the comparer.</summary>
|
||||||
|
public static SemanticVersionComparer Instance { get; } = new SemanticVersionComparer();
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Compare(ISemanticVersion x, ISemanticVersion y)
|
||||||
|
{
|
||||||
|
if (object.ReferenceEquals(x, y))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (x is null)
|
||||||
|
return -1;
|
||||||
|
if (y is null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return x.CompareTo(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,5 +46,12 @@ namespace StardewModdingAPI.Toolkit.Utilities
|
||||||
{
|
{
|
||||||
return LowLevelEnvironmentUtility.GetExecutableName(platform.ToString());
|
return LowLevelEnvironmentUtility.GetExecutableName(platform.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Get whether an executable is 64-bit.</summary>
|
||||||
|
/// <param name="path">The absolute path to the assembly file.</param>
|
||||||
|
public static bool Is64BitAssembly(string path)
|
||||||
|
{
|
||||||
|
return LowLevelEnvironmentUtility.Is64BitAssembly(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,10 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.OrdinalIgnoreCase));
|
ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||||
bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false;
|
bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false;
|
||||||
|
|
||||||
|
// SMAPI versions with a '-beta' tag indicate major changes that may need beta mod versions.
|
||||||
|
// This doesn't apply to normal prerelease versions which have an '-alpha' tag.
|
||||||
|
bool isSmapiBeta = apiVersion.IsPrerelease() && apiVersion.PrereleaseTag.StartsWith("beta");
|
||||||
|
|
||||||
// get latest versions
|
// get latest versions
|
||||||
ModEntryModel result = new ModEntryModel { ID = search.ID };
|
ModEntryModel result = new ModEntryModel { ID = search.ID };
|
||||||
IList<string> errors = new List<string>();
|
IList<string> errors = new List<string>();
|
||||||
|
@ -198,7 +202,7 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
List<ModEntryVersionModel> updates = new List<ModEntryVersionModel>();
|
List<ModEntryVersionModel> updates = new List<ModEntryVersionModel>();
|
||||||
if (this.IsRecommendedUpdate(installedVersion, main?.Version, useBetaChannel: true))
|
if (this.IsRecommendedUpdate(installedVersion, main?.Version, useBetaChannel: true))
|
||||||
updates.Add(main);
|
updates.Add(main);
|
||||||
if (this.IsRecommendedUpdate(installedVersion, optional?.Version, useBetaChannel: installedVersion.IsPrerelease() || search.IsBroken))
|
if (this.IsRecommendedUpdate(installedVersion, optional?.Version, useBetaChannel: isSmapiBeta || installedVersion.IsPrerelease() || search.IsBroken))
|
||||||
updates.Add(optional);
|
updates.Add(optional);
|
||||||
if (this.IsRecommendedUpdate(installedVersion, unofficial?.Version, useBetaChannel: true))
|
if (this.IsRecommendedUpdate(installedVersion, unofficial?.Version, useBetaChannel: true))
|
||||||
updates.Add(unofficial);
|
updates.Add(unofficial);
|
||||||
|
|
|
@ -110,41 +110,70 @@ namespace StardewModdingAPI.Web.Framework
|
||||||
main = null;
|
main = null;
|
||||||
preview = null;
|
preview = null;
|
||||||
|
|
||||||
ISemanticVersion ParseVersion(string raw)
|
// parse all versions from the mod page
|
||||||
|
IEnumerable<(string name, string description, ISemanticVersion version)> GetAllVersions()
|
||||||
{
|
{
|
||||||
raw = this.NormalizeVersion(raw);
|
if (mod != null)
|
||||||
return this.GetMappedVersion(raw, mapRemoteVersions, allowNonStandardVersions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mod != null)
|
|
||||||
{
|
|
||||||
// get mod version
|
|
||||||
if (subkey == null)
|
|
||||||
main = ParseVersion(mod.Version);
|
|
||||||
|
|
||||||
// get file versions
|
|
||||||
foreach (IModDownload download in mod.Downloads)
|
|
||||||
{
|
{
|
||||||
// check for subkey if specified
|
ISemanticVersion ParseAndMapVersion(string raw)
|
||||||
if (subkey != null && download.Name?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true && download.Description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true)
|
{
|
||||||
|
raw = this.NormalizeVersion(raw);
|
||||||
|
return this.GetMappedVersion(raw, mapRemoteVersions, allowNonStandardVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get mod version
|
||||||
|
ISemanticVersion modVersion = ParseAndMapVersion(mod.Version);
|
||||||
|
if (modVersion != null)
|
||||||
|
yield return (name: null, description: null, version: ParseAndMapVersion(mod.Version));
|
||||||
|
|
||||||
|
// get file versions
|
||||||
|
foreach (IModDownload download in mod.Downloads)
|
||||||
|
{
|
||||||
|
ISemanticVersion cur = ParseAndMapVersion(download.Version);
|
||||||
|
if (cur != null)
|
||||||
|
yield return (download.Name, download.Description, cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var versions = GetAllVersions()
|
||||||
|
.OrderByDescending(p => p.version, SemanticVersionComparer.Instance)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// get main + preview versions
|
||||||
|
void TryGetVersions(out ISemanticVersion mainVersion, out ISemanticVersion previewVersion, Func<(string name, string description, ISemanticVersion version), bool> filter = null)
|
||||||
|
{
|
||||||
|
mainVersion = null;
|
||||||
|
previewVersion = null;
|
||||||
|
|
||||||
|
// get latest main + preview version
|
||||||
|
foreach (var entry in versions)
|
||||||
|
{
|
||||||
|
if (filter?.Invoke(entry) == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// parse version
|
if (entry.version.IsPrerelease())
|
||||||
ISemanticVersion cur = ParseVersion(download.Version);
|
previewVersion ??= entry.version;
|
||||||
if (cur == null)
|
else
|
||||||
continue;
|
mainVersion ??= entry.version;
|
||||||
|
|
||||||
// track highest versions
|
if (mainVersion != null)
|
||||||
if (main == null || cur.IsNewerThan(main))
|
break; // any other values will be older
|
||||||
main = cur;
|
|
||||||
if (cur.IsPrerelease() && (preview == null || cur.IsNewerThan(preview)))
|
|
||||||
preview = cur;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preview != null && !preview.IsNewerThan(main))
|
// normalize values
|
||||||
preview = null;
|
if (previewVersion is not null)
|
||||||
|
{
|
||||||
|
mainVersion ??= previewVersion; // if every version is prerelease, latest one is the main version
|
||||||
|
if (!previewVersion.IsNewerThan(mainVersion))
|
||||||
|
previewVersion = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subkey is not null)
|
||||||
|
TryGetVersions(out main, out preview, entry => entry.name?.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true || entry.description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true);
|
||||||
|
if (main is null)
|
||||||
|
TryGetVersions(out main, out preview);
|
||||||
|
|
||||||
return main != null;
|
return main != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>SMAPI.Web</AssemblyName>
|
<AssemblyName>SMAPI.Web</AssemblyName>
|
||||||
<RootNamespace>StardewModdingAPI.Web</RootNamespace>
|
<RootNamespace>StardewModdingAPI.Web</RootNamespace>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
|
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
|
||||||
|
|
||||||
bool hasBeta = Model.BetaVersion != null;
|
bool hasBeta = Model.BetaVersion != null;
|
||||||
string betaLabel = "SDV @Model.BetaVersion only";
|
string betaLabel = $"SDV {Model.BetaVersion} only";
|
||||||
}
|
}
|
||||||
@section Head {
|
@section Head {
|
||||||
<link rel="stylesheet" href="~/Content/css/mods.css?r=20200218" />
|
<link rel="stylesheet" href="~/Content/css/mods.css?r=20200218" />
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Mono.Cecil;
|
||||||
using StardewModdingAPI.Enums;
|
using StardewModdingAPI.Enums;
|
||||||
using StardewModdingAPI.Framework;
|
using StardewModdingAPI.Framework;
|
||||||
using StardewModdingAPI.Framework.ModLoading;
|
using StardewModdingAPI.Framework.ModLoading;
|
||||||
|
@ -61,7 +62,7 @@ namespace StardewModdingAPI
|
||||||
internal static int? LogScreenId { get; set; }
|
internal static int? LogScreenId { get; set; }
|
||||||
|
|
||||||
/// <summary>SMAPI's current raw semantic version.</summary>
|
/// <summary>SMAPI's current raw semantic version.</summary>
|
||||||
internal static string RawApiVersion = "3.12.2";
|
internal static string RawApiVersion = "3.12.3";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||||
|
@ -229,6 +230,32 @@ namespace StardewModdingAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Configure the Mono.Cecil assembly resolver.</summary>
|
||||||
|
/// <param name="resolver">The assembly resolver.</param>
|
||||||
|
internal static void ConfigureAssemblyResolver(AssemblyDefinitionResolver resolver)
|
||||||
|
{
|
||||||
|
// add search paths
|
||||||
|
resolver.AddSearchDirectory(Constants.ExecutionPath);
|
||||||
|
resolver.AddSearchDirectory(Constants.InternalFilesPath);
|
||||||
|
|
||||||
|
// add SMAPI explicitly
|
||||||
|
// Normally this would be handled automatically by the search paths, but for some reason there's a specific
|
||||||
|
// case involving unofficial 64-bit Stardew Valley when launched through Steam (for some players only)
|
||||||
|
// where Mono.Cecil can't resolve references to SMAPI.
|
||||||
|
resolver.Add(AssemblyDefinition.ReadAssembly(typeof(SGame).Assembly.Location));
|
||||||
|
|
||||||
|
// make sure game assembly names can be resolved
|
||||||
|
// The game assembly can have one of three names depending how the mod was compiled:
|
||||||
|
// - 'StardewValley': assembly name on Linux/macOS;
|
||||||
|
// - 'Stardew Valley': assembly name on Windows;
|
||||||
|
// - 'Netcode': an assembly that's separate on Windows only.
|
||||||
|
resolver.Add(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley"
|
||||||
|
#if !SMAPI_FOR_WINDOWS
|
||||||
|
, "Netcode"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
|
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
|
||||||
/// <param name="targetPlatform">The target game platform.</param>
|
/// <param name="targetPlatform">The target game platform.</param>
|
||||||
/// <param name="framework">The game framework running the game.</param>
|
/// <param name="framework">The game framework running the game.</param>
|
||||||
|
|
|
@ -109,9 +109,12 @@ namespace StardewModdingAPI.Framework.Logging
|
||||||
output.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message);
|
output.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message);
|
||||||
Console.SetOut(output);
|
Console.SetOut(output);
|
||||||
|
|
||||||
// enable Unicode handling
|
// enable Unicode handling on Windows
|
||||||
|
// (the terminal defaults to UTF-8 on Linux/macOS)
|
||||||
|
#if SMAPI_FOR_WINDOWS
|
||||||
Console.InputEncoding = Encoding.Unicode;
|
Console.InputEncoding = Encoding.Unicode;
|
||||||
Console.OutputEncoding = Encoding.Unicode;
|
Console.OutputEncoding = Encoding.Unicode;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a monitor instance derived from SMAPI's current settings.</summary>
|
/// <summary>Get a monitor instance derived from SMAPI's current settings.</summary>
|
||||||
|
@ -162,8 +165,6 @@ namespace StardewModdingAPI.Framework.Logging
|
||||||
// keep console thread alive while the game is running
|
// keep console thread alive while the game is running
|
||||||
while (continueWhile())
|
while (continueWhile())
|
||||||
Thread.Sleep(1000 / 10);
|
Thread.Sleep(1000 / 10);
|
||||||
if (inputThread.ThreadState == ThreadState.Running)
|
|
||||||
inputThread.Abort();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Show a 'press any key to exit' message, and exit when they press a key.</summary>
|
/// <summary>Show a 'press any key to exit' message, and exit when they press a key.</summary>
|
||||||
|
|
|
@ -58,7 +58,11 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing).");
|
throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing).");
|
||||||
|
|
||||||
path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path));
|
path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path));
|
||||||
this.JsonHelper.WriteJsonFile(path, data);
|
|
||||||
|
if (data != null)
|
||||||
|
this.JsonHelper.WriteJsonFile(path, data);
|
||||||
|
else
|
||||||
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
|
|
|
@ -55,6 +55,12 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
return this.Translator.Get(key, tokens);
|
return this.Translator.Get(key, tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, Translation> GetInAllLocales(string key, bool withFallback = false)
|
||||||
|
{
|
||||||
|
return this.Translator.GetInAllLocales(key, withFallback);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Set the translations to use.</summary>
|
/// <summary>Set the translations to use.</summary>
|
||||||
/// <param name="translations">The translations to use.</param>
|
/// <param name="translations">The translations to use.</param>
|
||||||
internal TranslationHelper SetTranslations(IDictionary<string, IDictionary<string, string>> translations)
|
internal TranslationHelper SetTranslations(IDictionary<string, IDictionary<string, string>> translations)
|
||||||
|
|
|
@ -21,11 +21,17 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
public void Add(params AssemblyDefinition[] assemblies)
|
public void Add(params AssemblyDefinition[] assemblies)
|
||||||
{
|
{
|
||||||
foreach (AssemblyDefinition assembly in assemblies)
|
foreach (AssemblyDefinition assembly in assemblies)
|
||||||
{
|
this.Add(assembly, assembly.Name.Name, assembly.Name.FullName);
|
||||||
this.RegisterAssembly(assembly);
|
}
|
||||||
this.Lookup[assembly.Name.Name] = assembly;
|
|
||||||
this.Lookup[assembly.Name.FullName] = assembly;
|
/// <summary>Add known assemblies to the resolver.</summary>
|
||||||
}
|
/// <param name="assembly">The assembly to add.</param>
|
||||||
|
/// <param name="names">The assembly names for which it should be returned.</param>
|
||||||
|
public void Add(AssemblyDefinition assembly, params string[] names)
|
||||||
|
{
|
||||||
|
this.RegisterAssembly(assembly);
|
||||||
|
foreach (string name in names)
|
||||||
|
this.Lookup[name] = assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Resolve an assembly reference.</summary>
|
/// <summary>Resolve an assembly reference.</summary>
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using StardewModdingAPI.Framework.Exceptions;
|
using StardewModdingAPI.Framework.Exceptions;
|
||||||
using StardewModdingAPI.Framework.ModLoading.Framework;
|
using StardewModdingAPI.Framework.ModLoading.Framework;
|
||||||
|
using StardewModdingAPI.Framework.ModLoading.Symbols;
|
||||||
using StardewModdingAPI.Metadata;
|
using StardewModdingAPI.Metadata;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
@ -34,6 +35,12 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <summary>A minimal assembly definition resolver which resolves references to known loaded assemblies.</summary>
|
/// <summary>A minimal assembly definition resolver which resolves references to known loaded assemblies.</summary>
|
||||||
private readonly AssemblyDefinitionResolver AssemblyDefinitionResolver;
|
private readonly AssemblyDefinitionResolver AssemblyDefinitionResolver;
|
||||||
|
|
||||||
|
/// <summary>Provides assembly symbol readers for Mono.Cecil.</summary>
|
||||||
|
private readonly SymbolReaderProvider SymbolReaderProvider = new SymbolReaderProvider();
|
||||||
|
|
||||||
|
/// <summary>Provides assembly symbol writers for Mono.Cecil.</summary>
|
||||||
|
private readonly SymbolWriterProvider SymbolWriterProvider = new SymbolWriterProvider();
|
||||||
|
|
||||||
/// <summary>The objects to dispose as part of this instance.</summary>
|
/// <summary>The objects to dispose as part of this instance.</summary>
|
||||||
private readonly HashSet<IDisposable> Disposables = new HashSet<IDisposable>();
|
private readonly HashSet<IDisposable> Disposables = new HashSet<IDisposable>();
|
||||||
|
|
||||||
|
@ -59,9 +66,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
|
|
||||||
// init resolver
|
// init resolver
|
||||||
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
|
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
|
||||||
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.ExecutionPath);
|
Constants.ConfigureAssemblyResolver(this.AssemblyDefinitionResolver);
|
||||||
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.InternalFilesPath);
|
|
||||||
this.AssemblyDefinitionResolver.Add(AssemblyDefinition.ReadAssembly(typeof(SGame).Assembly.Location)); // for some reason Mono.Cecil can't resolve SMAPI in very specific cases involving unofficial 64-bit Stardew Valley when launched through Steam (for some players only)
|
|
||||||
|
|
||||||
// generate type => assembly lookup for types which should be rewritten
|
// generate type => assembly lookup for types which should be rewritten
|
||||||
this.TypeAssemblies = new Dictionary<string, Assembly>();
|
this.TypeAssemblies = new Dictionary<string, Assembly>();
|
||||||
|
@ -136,20 +141,12 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
if (!oneAssembly)
|
if (!oneAssembly)
|
||||||
this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace);
|
this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace);
|
||||||
|
|
||||||
// load PDB file if present
|
|
||||||
byte[] symbols;
|
|
||||||
{
|
|
||||||
string symbolsPath = Path.Combine(Path.GetDirectoryName(assemblyPath), Path.GetFileNameWithoutExtension(assemblyPath)) + ".pdb";
|
|
||||||
symbols = File.Exists(symbolsPath)
|
|
||||||
? File.ReadAllBytes(symbolsPath)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load assembly
|
// load assembly
|
||||||
using MemoryStream outStream = new MemoryStream();
|
using MemoryStream outAssemblyStream = new MemoryStream();
|
||||||
assembly.Definition.Write(outStream);
|
using MemoryStream outSymbolStream = new MemoryStream();
|
||||||
byte[] bytes = outStream.ToArray();
|
assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider });
|
||||||
lastAssembly = Assembly.Load(bytes, symbols);
|
byte[] bytes = outAssemblyStream.ToArray();
|
||||||
|
lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -236,10 +233,15 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
yield break; // not a local assembly
|
yield break; // not a local assembly
|
||||||
|
|
||||||
// read assembly
|
// read assembly and symbols
|
||||||
byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
|
byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
|
||||||
Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes));
|
Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes));
|
||||||
AssemblyDefinition assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true }));
|
{
|
||||||
|
FileInfo symbolsFile = new FileInfo(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb");
|
||||||
|
if (symbolsFile.Exists)
|
||||||
|
this.SymbolReaderProvider.TryAddSymbolData(file.Name, () => this.TrackForDisposal(symbolsFile.OpenRead()));
|
||||||
|
}
|
||||||
|
AssemblyDefinition assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true, ReadSymbols = true, SymbolReaderProvider = this.SymbolReaderProvider }));
|
||||||
|
|
||||||
// skip if already visited
|
// skip if already visited
|
||||||
if (visitedAssemblyNames.Contains(assembly.Name.Name))
|
if (visitedAssemblyNames.Contains(assembly.Name.Name))
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
using System.IO;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
using Mono.Cecil.Pdb;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.ModLoading.Symbols
|
||||||
|
{
|
||||||
|
/// <summary>Reads symbol data for an assembly.</summary>
|
||||||
|
internal class SymbolReader : ISymbolReader
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <summary>The module for which to read symbols.</summary>
|
||||||
|
private readonly ModuleDefinition Module;
|
||||||
|
|
||||||
|
/// <summary>The symbol file stream.</summary>
|
||||||
|
private readonly Stream Stream;
|
||||||
|
|
||||||
|
/// <summary>The underlying symbol reader.</summary>
|
||||||
|
private ISymbolReader Reader;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="module">The module for which to read symbols.</param>
|
||||||
|
/// <param name="stream">The symbol file stream.</param>
|
||||||
|
public SymbolReader(ModuleDefinition module, Stream stream)
|
||||||
|
{
|
||||||
|
this.Module = module;
|
||||||
|
this.Stream = stream;
|
||||||
|
this.Reader = new NativePdbReaderProvider().GetSymbolReader(module, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get the symbol writer provider for the assembly.</summary>
|
||||||
|
public ISymbolWriterProvider GetWriterProvider()
|
||||||
|
{
|
||||||
|
return new PortablePdbWriterProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Process a debug header in the symbol file.</summary>
|
||||||
|
/// <param name="header">The debug header.</param>
|
||||||
|
public bool ProcessDebugHeader(ImageDebugHeader header)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.Reader.ProcessDebugHeader(header);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.Reader.Dispose();
|
||||||
|
this.Reader = new PortablePdbReaderProvider().GetSymbolReader(this.Module, this.Stream);
|
||||||
|
return this.Reader.ProcessDebugHeader(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Read the method debug information for a method in the assembly.</summary>
|
||||||
|
/// <param name="method">The method definition.</param>
|
||||||
|
public MethodDebugInformation Read(MethodDefinition method)
|
||||||
|
{
|
||||||
|
return this.Reader.Read(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Reader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.ModLoading.Symbols
|
||||||
|
{
|
||||||
|
/// <summary>Provides assembly symbol readers for Mono.Cecil.</summary>
|
||||||
|
internal class SymbolReaderProvider : ISymbolReaderProvider
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <summary>The underlying symbol reader provider.</summary>
|
||||||
|
private readonly ISymbolReaderProvider BaseProvider = new DefaultSymbolReaderProvider(throwIfNoSymbol: false);
|
||||||
|
|
||||||
|
/// <summary>The symbol data loaded by absolute assembly path.</summary>
|
||||||
|
private readonly Dictionary<string, Stream> SymbolsByAssemblyPath = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Add the symbol file for a given assembly name, if it's not already registered.</summary>
|
||||||
|
/// <param name="fileName">The assembly file name.</param>
|
||||||
|
/// <param name="getSymbolStream">Get the raw file stream for the symbols.</param>
|
||||||
|
public void TryAddSymbolData(string fileName, Func<Stream> getSymbolStream)
|
||||||
|
{
|
||||||
|
if (!this.SymbolsByAssemblyPath.ContainsKey(fileName))
|
||||||
|
this.SymbolsByAssemblyPath.Add(fileName, getSymbolStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a symbol reader for a given module and assembly name.</summary>
|
||||||
|
/// <param name="module">The loaded assembly module.</param>
|
||||||
|
/// <param name="fileName">The assembly file name.</param>
|
||||||
|
public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
|
||||||
|
{
|
||||||
|
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
|
||||||
|
? new SymbolReader(module, symbolData)
|
||||||
|
: this.BaseProvider.GetSymbolReader(module, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a symbol reader for a given module and symbol stream.</summary>
|
||||||
|
/// <param name="module">The loaded assembly module.</param>
|
||||||
|
/// <param name="symbolStream">The loaded symbol file stream.</param>
|
||||||
|
public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
|
||||||
|
{
|
||||||
|
return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
|
||||||
|
? new SymbolReader(module, symbolData)
|
||||||
|
: this.BaseProvider.GetSymbolReader(module, symbolStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.IO;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.ModLoading.Symbols
|
||||||
|
{
|
||||||
|
/// <summary>Provides assembly symbol writers for Mono.Cecil.</summary>
|
||||||
|
internal class SymbolWriterProvider : ISymbolWriterProvider
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <summary>The default symbol writer provider.</summary>
|
||||||
|
private readonly ISymbolWriterProvider DefaultProvider = new DefaultSymbolWriterProvider();
|
||||||
|
|
||||||
|
/// <summary>The symbol writer provider for the portable PDB format.</summary>
|
||||||
|
private readonly ISymbolWriterProvider PortablePdbProvider = new PortablePdbWriterProvider();
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Get a symbol writer for a given module and assembly path.</summary>
|
||||||
|
/// <param name="module">The loaded assembly module.</param>
|
||||||
|
/// <param name="fileName">The assembly name.</param>
|
||||||
|
public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName)
|
||||||
|
{
|
||||||
|
return this.DefaultProvider.GetSymbolWriter(module, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a symbol writer for a given module and symbol stream.</summary>
|
||||||
|
/// <param name="module">The loaded assembly module.</param>
|
||||||
|
/// <param name="symbolStream">The loaded symbol file stream.</param>
|
||||||
|
public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream)
|
||||||
|
{
|
||||||
|
// Not implemented in default native pdb writer, so fallback to portable
|
||||||
|
return this.PortablePdbProvider.GetSymbolWriter(module, symbolStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1704,12 +1704,21 @@ namespace StardewModdingAPI.Framework
|
||||||
// load as mod
|
// load as mod
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// get mod info
|
||||||
IManifest manifest = mod.Manifest;
|
IManifest manifest = mod.Manifest;
|
||||||
|
string assemblyPath = Path.Combine(mod.DirectoryPath, manifest.EntryDll);
|
||||||
|
|
||||||
|
// assert 64-bit
|
||||||
|
#if SMAPI_FOR_WINDOWS_64BIT_HACK
|
||||||
|
if (!EnvironmentUtility.Is64BitAssembly(assemblyPath))
|
||||||
|
{
|
||||||
|
errorReasonPhrase = "it needs to be updated for 64-bit mode.";
|
||||||
|
failReason = ModFailReason.LoadFailed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// load mod
|
// load mod
|
||||||
string assemblyPath = manifest?.EntryDll != null
|
|
||||||
? Path.Combine(mod.DirectoryPath, manifest.EntryDll)
|
|
||||||
: null;
|
|
||||||
Assembly modAssembly;
|
Assembly modAssembly;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,18 +46,10 @@ namespace StardewModdingAPI.Framework
|
||||||
this.LocaleEnum = localeEnum;
|
this.LocaleEnum = localeEnum;
|
||||||
|
|
||||||
this.ForLocale = new Dictionary<string, Translation>(StringComparer.OrdinalIgnoreCase);
|
this.ForLocale = new Dictionary<string, Translation>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (string next in this.GetRelevantLocales(this.Locale))
|
foreach (string key in this.GetAllKeysRaw())
|
||||||
{
|
{
|
||||||
// skip if locale not defined
|
string text = this.GetRaw(key, locale, withFallback: true);
|
||||||
if (!this.All.TryGetValue(next, out IDictionary<string, string> translations))
|
this.ForLocale.Add(key, new Translation(this.Locale, key, text));
|
||||||
continue;
|
|
||||||
|
|
||||||
// add missing translations
|
|
||||||
foreach (var pair in translations)
|
|
||||||
{
|
|
||||||
if (!this.ForLocale.ContainsKey(pair.Key))
|
|
||||||
this.ForLocale.Add(pair.Key, new Translation(this.Locale, pair.Key, pair.Value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +75,25 @@ namespace StardewModdingAPI.Framework
|
||||||
return this.Get(key).Tokens(tokens);
|
return this.Get(key).Tokens(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a translation in every locale for which it's defined.</summary>
|
||||||
|
/// <param name="key">The translation key.</param>
|
||||||
|
/// <param name="withFallback">Whether to add duplicate translations for locale fallback. For example, if a translation is defined in <c>default.json</c> but not <c>fr.json</c>, setting this to true will add a <c>fr</c> entry which duplicates the default text.</param>
|
||||||
|
public IDictionary<string, Translation> GetInAllLocales(string key, bool withFallback)
|
||||||
|
{
|
||||||
|
IDictionary<string, Translation> translations = new Dictionary<string, Translation>();
|
||||||
|
|
||||||
|
foreach (var localeSet in this.All)
|
||||||
|
{
|
||||||
|
string locale = localeSet.Key;
|
||||||
|
string text = this.GetRaw(key, locale, withFallback);
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
translations[locale] = new Translation(locale, key, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return translations;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Set the translations to use.</summary>
|
/// <summary>Set the translations to use.</summary>
|
||||||
/// <param name="translations">The translations to use.</param>
|
/// <param name="translations">The translations to use.</param>
|
||||||
internal Translator SetTranslations(IDictionary<string, IDictionary<string, string>> translations)
|
internal Translator SetTranslations(IDictionary<string, IDictionary<string, string>> translations)
|
||||||
|
@ -102,6 +113,38 @@ namespace StardewModdingAPI.Framework
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Get all translation keys in the underlying translation data, ignoring the <see cref="ForLocale"/> cache.</summary>
|
||||||
|
private IEnumerable<string> GetAllKeysRaw()
|
||||||
|
{
|
||||||
|
return new HashSet<string>(
|
||||||
|
this.All.SelectMany(p => p.Value.Keys),
|
||||||
|
StringComparer.OrdinalIgnoreCase
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a translation from the underlying translation data, ignoring the <see cref="ForLocale"/> cache.</summary>
|
||||||
|
/// <param name="key">The translation key.</param>
|
||||||
|
/// <param name="locale">The locale to get.</param>
|
||||||
|
/// <param name="withFallback">Whether to add duplicate translations for locale fallback. For example, if a translation is defined in <c>default.json</c> but not <c>fr.json</c>, setting this to true will add a <c>fr</c> entry which duplicates the default text.</param>
|
||||||
|
private string GetRaw(string key, string locale, bool withFallback)
|
||||||
|
{
|
||||||
|
foreach (string next in this.GetRelevantLocales(locale))
|
||||||
|
{
|
||||||
|
string translation = null;
|
||||||
|
bool hasTranslation =
|
||||||
|
this.All.TryGetValue(next, out IDictionary<string, string> translations)
|
||||||
|
&& translations.TryGetValue(key, out translation);
|
||||||
|
|
||||||
|
if (hasTranslation)
|
||||||
|
return translation;
|
||||||
|
|
||||||
|
if (!withFallback)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get the locales which can provide translations for the given locale, in precedence order.</summary>
|
/// <summary>Get the locales which can provide translations for the given locale, in precedence order.</summary>
|
||||||
/// <param name="locale">The locale for which to find valid locales.</param>
|
/// <param name="locale">The locale for which to find valid locales.</param>
|
||||||
private IEnumerable<string> GetRelevantLocales(string locale)
|
private IEnumerable<string> GetRelevantLocales(string locale)
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace StardewModdingAPI
|
||||||
/// <summary>Save data to a JSON file in the mod's folder.</summary>
|
/// <summary>Save data to a JSON file in the mod's folder.</summary>
|
||||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||||
/// <param name="path">The file path relative to the mod folder.</param>
|
/// <param name="path">The file path relative to the mod folder.</param>
|
||||||
/// <param name="data">The arbitrary data to save.</param>
|
/// <param name="data">The arbitrary data to save, or <c>null</c> to delete the file.</param>
|
||||||
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
/// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
|
||||||
void WriteJsonFile<TModel>(string path, TModel data) where TModel : class;
|
void WriteJsonFile<TModel>(string path, TModel data) where TModel : class;
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ namespace StardewModdingAPI
|
||||||
/// <summary>Save arbitrary data to the current save slot. This is only possible if a save has been loaded, and the data will be lost if the player exits without saving the current day.</summary>
|
/// <summary>Save arbitrary data to the current save slot. This is only possible if a save has been loaded, and the data will be lost if the player exits without saving the current day.</summary>
|
||||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||||
/// <param name="key">The unique key identifying the data.</param>
|
/// <param name="key">The unique key identifying the data.</param>
|
||||||
/// <param name="data">The arbitrary data to save.</param>
|
/// <param name="data">The arbitrary data to save, or <c>null</c> to remove the entry.</param>
|
||||||
/// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception>
|
/// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception>
|
||||||
void WriteSaveData<TModel>(string key, TModel data) where TModel : class;
|
void WriteSaveData<TModel>(string key, TModel data) where TModel : class;
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ namespace StardewModdingAPI
|
||||||
/// <summary>Save arbitrary data to the local computer, synchronised by GOG/Steam if applicable.</summary>
|
/// <summary>Save arbitrary data to the local computer, synchronised by GOG/Steam if applicable.</summary>
|
||||||
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
|
||||||
/// <param name="key">The unique key identifying the data.</param>
|
/// <param name="key">The unique key identifying the data.</param>
|
||||||
/// <param name="data">The arbitrary data to save.</param>
|
/// <param name="data">The arbitrary data to save, or <c>null</c> to delete the file.</param>
|
||||||
void WriteGlobalData<TModel>(string key, TModel data) where TModel : class;
|
void WriteGlobalData<TModel>(string key, TModel data) where TModel : class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
|
||||||
namespace StardewModdingAPI
|
namespace StardewModdingAPI
|
||||||
|
@ -30,5 +30,10 @@ namespace StardewModdingAPI
|
||||||
/// <param name="key">The translation key.</param>
|
/// <param name="key">The translation key.</param>
|
||||||
/// <param name="tokens">An object containing token key/value pairs. This can be an anonymous object (like <c>new { value = 42, name = "Cranberries" }</c>), a dictionary, or a class instance.</param>
|
/// <param name="tokens">An object containing token key/value pairs. This can be an anonymous object (like <c>new { value = 42, name = "Cranberries" }</c>), a dictionary, or a class instance.</param>
|
||||||
Translation Get(string key, object tokens);
|
Translation Get(string key, object tokens);
|
||||||
|
|
||||||
|
/// <summary>Get a translation in every locale for which it's defined.</summary>
|
||||||
|
/// <param name="key">The translation key.</param>
|
||||||
|
/// <param name="withFallback">Whether to add duplicate translations for locale fallback. For example, if a translation is defined in <c>default.json</c> but not <c>fr.json</c>, setting this to true will add a <c>fr</c> entry which duplicates the default text.</param>
|
||||||
|
IDictionary<string, Translation> GetInAllLocales(string key, bool withFallback = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue