From 727d75ae728ba6cc8fc070524264c454aac8404f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Aug 2021 21:26:10 -0400 Subject: update to .NET 5 and official 64-bit --- src/SMAPI.Toolkit/Framework/Constants.cs | 9 +++++++++ src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs | 5 +---- src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs | 9 --------- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 6 +++--- src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs | 7 ------- src/SMAPI.Toolkit/Utilities/PathUtilities.cs | 15 ++++++++++----- 6 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 src/SMAPI.Toolkit/Framework/Constants.cs (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Toolkit/Framework/Constants.cs b/src/SMAPI.Toolkit/Framework/Constants.cs new file mode 100644 index 00000000..55f26582 --- /dev/null +++ b/src/SMAPI.Toolkit/Framework/Constants.cs @@ -0,0 +1,9 @@ +namespace StardewModdingAPI.Toolkit.Framework +{ + /// Contains the SMAPI installer's constants and assumptions. + internal static class Constants + { + /// The name of the game's main DLL, used to detect game folders. + public const string GameDllName = "Stardew Valley.dll"; + } +} diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index c90fc1d3..7553c07f 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -56,10 +56,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning { return dir.Exists - && ( - dir.EnumerateFiles("StardewValley.exe").Any() - || dir.EnumerateFiles("Stardew Valley.exe").Any() - ); + && dir.EnumerateFiles("Stardew Valley.dll").Any(); } diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 2636aae0..8b6eb5fb 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -80,15 +80,6 @@ namespace StardewModdingAPI.Toolkit.Framework return name; } - /// Get the name of the Stardew Valley executable. - /// The current platform. - public static string GetExecutableName(string platform) - { - return platform == nameof(Platform.Windows) - ? "Stardew Valley.exe" - : "StardewValley.exe"; - } - /// Get whether an executable is 64-bit. /// The absolute path to the assembly file. public static bool Is64BitAssembly(string path) diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 3d87c169..ec27bf79 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -2,7 +2,7 @@ StardewModdingAPI.Toolkit A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods. - net452;netstandard2.0 + net5.0; netstandard2.0 true @@ -12,8 +12,8 @@ - - + + diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs index 6de79a85..7536337a 100644 --- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -40,13 +40,6 @@ namespace StardewModdingAPI.Toolkit.Utilities return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString()); } - /// Get the name of the Stardew Valley executable. - /// The current platform. - public static string GetExecutableName(Platform platform) - { - return LowLevelEnvironmentUtility.GetExecutableName(platform.ToString()); - } - /// Get whether an executable is 64-bit. /// The absolute path to the assembly file. public static bool Is64BitAssembly(string path) diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index 020ebc6d..c17bb354 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -88,14 +88,18 @@ namespace StardewModdingAPI.Toolkit.Utilities /// Get a directory or file path relative to a given source path. If no relative path is possible (e.g. the paths are on different drives), an absolute path is returned. /// The source folder path. /// The target folder or file path. - /// - /// - /// NOTE: this is a heuristic implementation that works in the cases SMAPI needs it for, but it doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between UNC paths on Windows). This should be replaced with the more comprehensive Path.GetRelativePath if the game ever migrates to .NET Core. - /// - /// [Pure] public static string GetRelativePath(string sourceDir, string targetPath) { +#if NET5_0 + return Path.GetRelativePath(sourceDir, targetPath); +#else + // NOTE: + // this is a heuristic implementation that works in the cases SMAPI needs it for, but it + // doesn't handle all edge cases (e.g. case-sensitivity on Linux, or traversing between + // UNC paths on Windows). SMAPI and mods will use the more robust .NET 5 version anyway + // though, this is only for compatibility with the mod build package. + // convert to URIs Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); @@ -123,6 +127,7 @@ namespace StardewModdingAPI.Toolkit.Utilities } return relative; +#endif } /// Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain ../). -- cgit From 8321cbf6eba7ca3fa46d6e452fd9ebff6baa99d0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 12 Aug 2021 21:26:10 -0400 Subject: update for asset name format change MonoGame uses Linux-style paths for assets on all platforms, which breaks the previous equivalence between path and asset name formats. --- src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs | 8 ++------ src/SMAPI.Toolkit/Utilities/PathUtilities.cs | 2 +- src/SMAPI/Framework/Content/ContentCache.cs | 14 ++++---------- 3 files changed, 7 insertions(+), 17 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs index c18f47a5..ab4c2618 100644 --- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs +++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs @@ -182,18 +182,14 @@ namespace SMAPI.Tests.Utilities [TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))] public void NormalizeAssetName(SamplePath path) { - if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith("/") || path.OriginalPath.StartsWith("\\")) + if (Path.IsPathRooted(path.OriginalPath) || path.OriginalPath.StartsWith('/') || path.OriginalPath.StartsWith('\\')) Assert.Ignore("Absolute paths can't be used as asset names."); // act string normalized = PathUtilities.NormalizeAssetName(path.OriginalPath); // assert -#if SMAPI_FOR_WINDOWS - Assert.AreEqual(path.NormalizedOnWindows, normalized); -#else - Assert.AreEqual(path.NormalizedOnUnix, normalized); -#endif + Assert.AreEqual(path.NormalizedOnUnix, normalized); // MonoGame uses the Linux format } /**** diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index c17bb354..1d378042 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Toolkit.Utilities public static readonly char PreferredPathSeparator = Path.DirectorySeparatorChar; /// The preferred directory separator character in an asset key. - public static readonly char PreferredAssetSeparator = PathUtilities.PreferredPathSeparator; + public static readonly char PreferredAssetSeparator = '/'; /********* diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 87d15e32..8e0c6228 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -17,9 +17,6 @@ namespace StardewModdingAPI.Framework.Content /// The underlying asset cache. private readonly IDictionary Cache; - /// Applies platform-specific asset key normalization so it's consistent with the underlying cache. - private readonly Func NormalizeAssetNameForPlatform; - /********* ** Accessors @@ -47,11 +44,7 @@ namespace StardewModdingAPI.Framework.Content /// Simplifies access to private game code. public ContentCache(LocalizedContentManager contentManager, Reflector reflection) { - // init this.Cache = reflection.GetField>(contentManager, "loadedAssets").GetValue(); - - // get key normalization logic - this.NormalizeAssetNameForPlatform = PathUtilities.NormalizePath; //this.NormalizeAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load logic } /**** @@ -68,23 +61,24 @@ namespace StardewModdingAPI.Framework.Content /**** ** Normalize ****/ - /// Normalize path separators in a file path. For asset keys, see instead. + /// Normalize path separators in an asset name. /// The file path to normalize. [Pure] public string NormalizePathSeparators(string path) { - return PathUtilities.NormalizePath(path); + return PathUtilities.NormalizeAssetName(path); } /// Normalize a cache key so it's consistent with the underlying cache. /// The asset key. + /// This is equivalent to with added file extension logic. [Pure] public string NormalizeKey(string key) { key = this.NormalizePathSeparators(key); return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) ? key.Substring(0, key.Length - 4) - : this.NormalizeAssetNameForPlatform(key); + : key; } /**** -- cgit From 89c98223ebf6bfeee5ef587ab748995e09dd4310 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 18 Aug 2021 23:55:43 -0400 Subject: remove path-too-long exception handling The path length limit no longer applies in .NET 5. --- src/SMAPI.Installer/InteractiveInstaller.cs | 11 ----------- src/SMAPI.Toolkit/Utilities/PathUtilities.cs | 28 ---------------------------- src/SMAPI/Framework/Logging/LogManager.cs | 19 +------------------ 3 files changed, 1 insertion(+), 57 deletions(-) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 896017d3..714a959b 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -259,17 +259,6 @@ namespace StardewModdingApi.Installer Console.ReadLine(); return; } - - // game folder doesn't contain paths beyond the max limit - { - string[] tooLongPaths = PathUtilities.GetTooLongPaths(Path.Combine(paths.GamePath, "Mods")).ToArray(); - if (tooLongPaths.Any()) - { - this.PrintError($"SMAPI can't install to the detected game folder, because some of its files exceed the maximum {context.Platform} path length.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", tooLongPaths)}"); - Console.ReadLine(); - return; - } - } Console.Clear(); diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index 1d378042..2e9e5eac 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; using System.IO; using System.Linq; @@ -150,32 +149,5 @@ namespace StardewModdingAPI.Toolkit.Utilities { return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase); } - - /// Get the paths which exceed the OS length limit. - /// The root path to search. - internal static IEnumerable GetTooLongPaths(string rootPath) - { - if (!Directory.Exists(rootPath)) - return new string[0]; - - return Directory - .EnumerateFileSystemEntries(rootPath, "*.*", SearchOption.AllDirectories) - .Where(PathUtilities.IsPathTooLong); - } - - /// Get whether a file or directory path exceeds the OS path length limit. - /// The path to test. - internal static bool IsPathTooLong(string path) - { - try - { - _ = Path.GetFullPath(path); - return false; - } - catch (PathTooLongException) - { - return true; - } - } } } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index dbcf8934..5a291d0a 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -250,24 +250,7 @@ namespace StardewModdingAPI.Framework.Logging /// The exception details. public void LogFatalLaunchError(Exception exception) { - switch (exception) - { - // path too long exception - case PathTooLongException _: - { - string[] affectedPaths = PathUtilities.GetTooLongPaths(Constants.ModsPath).ToArray(); - string message = affectedPaths.Any() - ? $"SMAPI can't launch because some of your mod files exceed the maximum path length on {Constants.Platform}.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", affectedPaths)}" - : $"The game failed to launch: {exception.GetLogSummary()}"; - this.MonitorForGame.Log(message, LogLevel.Error); - } - break; - - // generic exception - default: - this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error); - break; - } + this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error); } /**** -- cgit From 294f2a311ea2cfb590529522c0afae8e5a4f44fd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 19 Aug 2021 00:40:12 -0400 Subject: fix error resolving native DLLs like libSkiaSharp --- src/SMAPI.Installer/InteractiveInstaller.cs | 8 ++++++++ src/SMAPI.Toolkit/Utilities/FileUtilities.cs | 13 +++++++++++++ src/SMAPI/Program.cs | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) (limited to 'src/SMAPI.Toolkit') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 714a959b..d8c27a2d 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -410,6 +410,14 @@ namespace StardewModdingApi.Installer } } + // copy the game's deps.json file + // (This is needed to resolve native DLLs like libSkiaSharp.) + File.Copy( + sourceFileName: Path.Combine(paths.GamePath, "Stardew Valley.deps.json"), + destFileName: Path.Combine(paths.GamePath, "StardewModdingAPI.deps.json"), + overwrite: true + ); + // create mods directory (if needed) if (!paths.ModsDir.Exists) { diff --git a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs index 7856fdb1..a6bf5929 100644 --- a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs @@ -1,4 +1,6 @@ +using System; using System.IO; +using System.Security.Cryptography; using System.Threading; namespace StardewModdingAPI.Toolkit.Utilities @@ -42,5 +44,16 @@ namespace StardewModdingAPI.Toolkit.Utilities if (entry.Exists) throw new IOException($"Timed out trying to delete {entry.FullName}"); } + + /// Get the MD5 hash for a file. + /// The absolute file path. + public static string GetFileHash(string absolutePath) + { + using FileStream stream = File.OpenRead(absolutePath); + using MD5 md5 = MD5.Create(); + + byte[] hash = md5.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } } } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 0417697b..67ff8322 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading; using StardewModdingAPI.Framework; using StardewModdingAPI.Toolkit.Serialization.Models; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI { @@ -33,6 +34,7 @@ namespace StardewModdingAPI Program.AssertGamePresent(); Program.AssertGameVersion(); Program.AssertSmapiVersions(); + Program.AssertDepsJson(); Program.Start(args); } catch (BadImageFormatException ex) when (ex.FileName == EarlyConstants.GameAssemblyName) @@ -130,6 +132,20 @@ namespace StardewModdingAPI } } + /// Assert that SMAPI's StardewModdingAPI.deps.json matches Stardew Valley.deps.json, fixing it if necessary. + /// This is needed to resolve native DLLs like libSkiaSharp. + private static void AssertDepsJson() + { + string sourcePath = Path.Combine(Constants.ExecutionPath, "Stardew Valley.deps.json"); + string targetPath = Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.deps.json"); + + if (!File.Exists(targetPath) || FileUtilities.GetFileHash(sourcePath) != FileUtilities.GetFileHash(targetPath)) + { + File.Copy(sourcePath, targetPath, overwrite: true); + Program.PrintErrorAndExit($"The '{Path.GetFileName(targetPath)}' file didn't match the game's version. SMAPI fixed it automatically, but you must restart SMAPI for the change to take effect."); + } + } + /// Initialize SMAPI and launch the game. /// The command-line arguments. /// This method is separate from because that can't contain any references to assemblies loaded by (e.g. via ), or Mono will incorrectly show an assembly resolution error before assembly resolution is set up. -- cgit