From 8db280d8748c9285e79df9694f5056e38cce89af Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 Jan 2017 22:30:07 -0500 Subject: expose SemanticVersion constructor that parses a string --- src/StardewModdingAPI/SemanticVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index daefda51..c29f2cf7 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -56,7 +56,7 @@ namespace StardewModdingAPI /// Construct an instance. /// The semantic version string. /// The is not a valid semantic version. - internal SemanticVersion(string version) + public SemanticVersion(string version) { var match = SemanticVersion.Regex.Match(version); if (!match.Success) -- cgit From 8c6dca95dae1878610c4cdf18a2f98578e3c577e Mon Sep 17 00:00:00 2001 From: "Bpendragon (David Camp)" Date: Thu, 2 Feb 2017 13:04:29 -0800 Subject: Corrected CurrentLocationChanged's "NewLocation" and "PriorLocation" descriptors, they displayed in Intellisense as the opposite of what they were. --- src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs index 443429aa..aa0bb377 100644 --- a/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs +++ b/src/StardewModdingAPI/Events/EventArgsCurrentLocationChanged.cs @@ -9,10 +9,10 @@ namespace StardewModdingAPI.Events /********* ** Accessors *********/ - /// The player's previous location. + /// The player's current location. public GameLocation NewLocation { get; private set; } - /// The player's current location. + /// The player's previous location. public GameLocation PriorLocation { get; private set; } -- cgit From ae7d9d6bc484bd27922e6652d116ce7ddd4b8104 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 2 Feb 2017 20:47:54 -0500 Subject: fix error when SMAPI tries to load Mac metadata files for DLLs --- release-notes.md | 3 +++ src/StardewModdingAPI/Program.cs | 3 +++ 2 files changed, 6 insertions(+) (limited to 'src/StardewModdingAPI') diff --git a/release-notes.md b/release-notes.md index 2ef8b332..0c6b8a89 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,6 +3,9 @@ ## 1.8 See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). +* For players: + * Fixed a rare issue where loading mods failed due to hidden metadata files on Mac. + * For mod developers: * You can now create a `SemanticVersion` from a version string. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ec3ccce7..08b5c636 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -464,6 +464,9 @@ namespace StardewModdingAPI bool succeeded = true; foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) { + if (Path.GetFileName(assemblyPath).StartsWith(".")) + continue; // skip hidden files (e.g. Mac sometimes copies with "._" prefix). + try { processedAssemblies.Add(modAssemblyLoader.ProcessAssemblyUnlessCached(assemblyPath)); -- cgit From 6ee14ecfbfd4abcb78b2c8db6ac220981e019f32 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 2 Feb 2017 23:22:54 -0500 Subject: rewrite mod assembly loading (#229) This greatly simplifies mod loading, eliminates the .cache folders by loading assemblies in memory, ensures DLLs are loaded in leaf-to-root order, and reduces log verbosity. These changes should address a range of issues, notably #221 and #226. --- .../Framework/AssemblyDefinitionResolver.cs | 61 ++++++ src/StardewModdingAPI/Framework/AssemblyLoader.cs | 239 +++++++++++++++++++++ .../Framework/AssemblyParseResult.cs | 31 +++ .../AssemblyRewriting/AssemblyTypeRewriter.cs | 162 -------------- .../Framework/AssemblyRewriting/CacheEntry.cs | 61 ------ .../Framework/AssemblyRewriting/CachePaths.cs | 33 --- .../Framework/AssemblyRewriting/RewriteResult.cs | 49 ----- .../Framework/ModAssemblyLoader.cs | 143 ------------ src/StardewModdingAPI/Program.cs | 58 ++--- src/StardewModdingAPI/StardewModdingAPI.csproj | 10 +- 10 files changed, 353 insertions(+), 494 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs create mode 100644 src/StardewModdingAPI/Framework/AssemblyLoader.cs create mode 100644 src/StardewModdingAPI/Framework/AssemblyParseResult.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs delete mode 100644 src/StardewModdingAPI/Framework/ModAssemblyLoader.cs (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs b/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs new file mode 100644 index 00000000..b4e69fcd --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Mono.Cecil; + +namespace StardewModdingAPI.Framework +{ + /// A minimal assembly definition resolver which resolves references to known assemblies. + internal class AssemblyDefinitionResolver : DefaultAssemblyResolver + { + /********* + ** Properties + *********/ + /// The known assemblies. + private readonly IDictionary Loaded = new Dictionary(); + + + /********* + ** Public methods + *********/ + /// Add known assemblies to the resolver. + /// The known assemblies. + public void Add(params AssemblyDefinition[] assemblies) + { + foreach (AssemblyDefinition assembly in assemblies) + { + this.Loaded[assembly.Name.Name] = assembly; + this.Loaded[assembly.Name.FullName] = assembly; + } + } + + /// Resolve an assembly reference. + /// The assembly name. + public override AssemblyDefinition Resolve(AssemblyNameReference name) => this.ResolveName(name.Name) ?? base.Resolve(name); + + /// Resolve an assembly reference. + /// The assembly name. + /// The assembly reader parameters. + public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) => this.ResolveName(name.Name) ?? base.Resolve(name, parameters); + + /// Resolve an assembly reference. + /// The assembly full name (including version, etc). + public override AssemblyDefinition Resolve(string fullName) => this.ResolveName(fullName) ?? base.Resolve(fullName); + + /// Resolve an assembly reference. + /// The assembly full name (including version, etc). + /// The assembly reader parameters. + public override AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) => this.ResolveName(fullName) ?? base.Resolve(fullName, parameters); + + + /********* + ** Private methods + *********/ + /// Resolve a known assembly definition based on its short or full name. + /// The assembly's short or full name. + private AssemblyDefinition ResolveName(string name) + { + return this.Loaded.ContainsKey(name) + ? this.Loaded[name] + : null; + } + } +} diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs new file mode 100644 index 00000000..37f2764a --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using StardewModdingAPI.AssemblyRewriters; + +namespace StardewModdingAPI.Framework +{ + /// Preprocesses and loads mod assemblies. + internal class AssemblyLoader + { + /********* + ** Properties + *********/ + /// Metadata for mapping assemblies to the current platform. + private readonly PlatformAssemblyMap AssemblyMap; + + /// A type => assembly lookup for types which should be rewritten. + private readonly IDictionary TypeAssemblies; + + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The current game platform. + /// Encapsulates monitoring and logging. + public AssemblyLoader(Platform targetPlatform, IMonitor monitor) + { + this.Monitor = monitor; + this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform); + + // generate type => assembly lookup for types which should be rewritten + this.TypeAssemblies = new Dictionary(); + foreach (Assembly assembly in this.AssemblyMap.Targets) + { + ModuleDefinition module = this.AssemblyMap.TargetModules[assembly]; + foreach (TypeDefinition type in module.GetTypes()) + { + if (!type.IsPublic) + continue; // no need to rewrite + if (type.Namespace.Contains("<")) + continue; // ignore assembly metadata + this.TypeAssemblies[type.FullName] = assembly; + } + } + } + + /// Preprocess and load an assembly, writing modified assemblies to the cache folder if needed. + /// The assembly file path. + /// Returns the rewrite metadata for the preprocessed assembly. + public Assembly Load(string assemblyPath) + { + // get referenced local assemblies + AssemblyParseResult[] assemblies; + { + AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver(); + assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), new HashSet(), resolver).ToArray(); + if (!assemblies.Any()) + throw new InvalidOperationException($"Could not load '{assemblyPath}' because it doesn't exist."); + resolver.Add(assemblies.Select(p => p.Definition).ToArray()); + } + + // rewrite & load assemblies in leaf-to-root order + Assembly lastAssembly = null; + foreach (AssemblyParseResult assembly in assemblies) + { + this.Monitor.Log($"Loading {assembly.File.FullName}...", LogLevel.Trace); + this.RewriteAssembly(assembly.Definition); + using (MemoryStream outStream = new MemoryStream()) + { + assembly.Definition.Write(outStream); + byte[] bytes = outStream.ToArray(); + lastAssembly = Assembly.Load(bytes); + } + } + + // last assembly loaded is the root + return lastAssembly; + } + + /// Resolve an assembly by its name. + /// The assembly name. + /// + /// This implementation returns the first loaded assembly which matches the short form of + /// the assembly name, to resolve assembly resolution issues when rewriting + /// assemblies (especially with Mono). Since this is meant to be called on , + /// the implicit assumption is that loading the exact assembly failed. + /// + public Assembly ResolveAssembly(string name) + { + string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture) + return AppDomain.CurrentDomain + .GetAssemblies() + .FirstOrDefault(p => p.GetName().Name == shortName); + } + + + /********* + ** Private methods + *********/ + /**** + ** Assembly parsing + ****/ + /// Get a list of referenced local assemblies starting from the mod assembly, ordered from leaf to root. + /// The assembly file to load. + /// The assembly paths that should be skipped. + /// Returns the rewrite metadata for the preprocessed assembly. + private IEnumerable GetReferencedLocalAssemblies(FileInfo file, HashSet visitedAssemblyPaths, IAssemblyResolver assemblyResolver) + { + // validate + if (file.Directory == null) + throw new InvalidOperationException($"Could not get directory from file path '{file.FullName}'."); + if (visitedAssemblyPaths.Contains(file.FullName)) + yield break; // already visited + if (!file.Exists) + yield break; // not a local assembly + visitedAssemblyPaths.Add(file.FullName); + + // read assembly + byte[] assemblyBytes = File.ReadAllBytes(file.FullName); + AssemblyDefinition assembly; + using (Stream readStream = new MemoryStream(assemblyBytes)) + assembly = AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); + + // yield referenced assemblies + foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences) + { + FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); + foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyPaths, assemblyResolver)) + yield return result; + } + + // yield assembly + yield return new AssemblyParseResult(file, assembly); + } + + /**** + ** Assembly rewriting + ****/ + /// Rewrite the types referenced by an assembly. + /// The assembly to rewrite. + /// Returns whether the assembly was modified. + private bool RewriteAssembly(AssemblyDefinition assembly) + { + ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module + + // remove old assembly references + bool shouldRewrite = false; + for (int i = 0; i < module.AssemblyReferences.Count; i++) + { + if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) + { + shouldRewrite = true; + module.AssemblyReferences.RemoveAt(i); + i--; + } + } + if (!shouldRewrite) + return false; + + // add target assembly references + foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) + module.AssemblyReferences.Add(target); + + // rewrite type scopes to use target assemblies + IEnumerable typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); + foreach (TypeReference type in typeReferences) + this.ChangeTypeScope(type); + + // rewrite incompatible methods + IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray(); + foreach (MethodDefinition method in this.GetMethods(module)) + { + // skip methods with no rewritable method + bool hasMethodToRewrite = method.Body.Instructions.Any(op => (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) && methodRewriters.Any(rewriter => rewriter.ShouldRewrite((MethodReference)op.Operand))); + if (!hasMethodToRewrite) + continue; + + // rewrite method references + method.Body.SimplifyMacros(); + ILProcessor cil = method.Body.GetILProcessor(); + Instruction[] instructions = cil.Body.Instructions.ToArray(); + foreach (Instruction op in instructions) + { + if (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) + { + IMethodRewriter rewriter = methodRewriters.FirstOrDefault(p => p.ShouldRewrite((MethodReference)op.Operand)); + if (rewriter != null) + { + MethodReference methodRef = (MethodReference)op.Operand; + rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap); + } + } + } + method.Body.OptimizeMacros(); + } + return true; + } + + /// Get the correct reference to use for compatibility with the current platform. + /// The type reference to rewrite. + private void ChangeTypeScope(TypeReference type) + { + // check skip conditions + if (type == null || type.FullName.StartsWith("System.")) + return; + + // get assembly + Assembly assembly; + if (!this.TypeAssemblies.TryGetValue(type.FullName, out assembly)) + return; + + // replace scope + AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly]; + type.Scope = assemblyRef; + } + + /// Get all methods in a module. + /// The module to search. + private IEnumerable GetMethods(ModuleDefinition module) + { + return ( + from type in module.GetTypes() + where type.HasMethods + from method in type.Methods + where method.HasBody + select method + ); + } + } +} diff --git a/src/StardewModdingAPI/Framework/AssemblyParseResult.cs b/src/StardewModdingAPI/Framework/AssemblyParseResult.cs new file mode 100644 index 00000000..bff976aa --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyParseResult.cs @@ -0,0 +1,31 @@ +using System.IO; +using Mono.Cecil; + +namespace StardewModdingAPI.Framework +{ + /// Metadata about a parsed assembly definition. + internal class AssemblyParseResult + { + /********* + ** Accessors + *********/ + /// The original assembly file. + public readonly FileInfo File; + + /// The assembly definition. + public readonly AssemblyDefinition Definition; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The original assembly file. + /// The assembly definition. + public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly) + { + this.File = file; + this.Definition = assembly; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs deleted file mode 100644 index 9d4d6b11..00000000 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Cecil.Rocks; -using StardewModdingAPI.AssemblyRewriters; - -namespace StardewModdingAPI.Framework.AssemblyRewriting -{ - /// Rewrites type references. - internal class AssemblyTypeRewriter - { - /********* - ** Properties - *********/ - /// Metadata for mapping assemblies to the current . - private readonly PlatformAssemblyMap AssemblyMap; - - /// A type => assembly lookup for types which should be rewritten. - private readonly IDictionary TypeAssemblies; - - /// Encapsulates monitoring and logging. - private readonly IMonitor Monitor; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Metadata for mapping assemblies to the current . - /// Encapsulates monitoring and logging. - public AssemblyTypeRewriter(PlatformAssemblyMap assemblyMap, IMonitor monitor) - { - // save config - this.AssemblyMap = assemblyMap; - this.Monitor = monitor; - - // collect type => assembly lookup - this.TypeAssemblies = new Dictionary(); - foreach (Assembly assembly in assemblyMap.Targets) - { - ModuleDefinition module = this.AssemblyMap.TargetModules[assembly]; - foreach (TypeDefinition type in module.GetTypes()) - { - if (!type.IsPublic) - continue; // no need to rewrite - if (type.Namespace.Contains("<")) - continue; // ignore assembly metadata - this.TypeAssemblies[type.FullName] = assembly; - } - } - } - - /// Rewrite the types referenced by an assembly. - /// The assembly to rewrite. - /// Returns whether the assembly was modified. - public bool RewriteAssembly(AssemblyDefinition assembly) - { - ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module - - // remove old assembly references - bool shouldRewrite = false; - for (int i = 0; i < module.AssemblyReferences.Count; i++) - { - if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) - { - this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace); - shouldRewrite = true; - module.AssemblyReferences.RemoveAt(i); - i--; - } - } - if (!shouldRewrite) - return false; - - // add target assembly references - foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) - { - this.Monitor.Log($" adding reference to {target}", LogLevel.Trace); - module.AssemblyReferences.Add(target); - } - - // rewrite type scopes to use target assemblies - IEnumerable typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); - string lastTypeLogged = null; - foreach (TypeReference type in typeReferences) - { - this.ChangeTypeScope(type, shouldLog: type.FullName != lastTypeLogged); - lastTypeLogged = type.FullName; - } - - // rewrite incompatible methods - IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray(); - foreach (MethodDefinition method in this.GetMethods(module)) - { - // skip methods with no rewritable method - bool hasMethodToRewrite = method.Body.Instructions.Any(op => (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) && methodRewriters.Any(rewriter => rewriter.ShouldRewrite((MethodReference)op.Operand))); - if (!hasMethodToRewrite) - continue; - - // rewrite method references - method.Body.SimplifyMacros(); - ILProcessor cil = method.Body.GetILProcessor(); - Instruction[] instructions = cil.Body.Instructions.ToArray(); - foreach (Instruction op in instructions) - { - if (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) - { - IMethodRewriter rewriter = methodRewriters.FirstOrDefault(p => p.ShouldRewrite((MethodReference)op.Operand)); - if (rewriter != null) - { - MethodReference methodRef = (MethodReference)op.Operand; - this.Monitor.Log($"rewriting method reference {methodRef.DeclaringType.FullName}.{methodRef.Name}", LogLevel.Trace); - rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap); - } - } - } - method.Body.OptimizeMacros(); - } - return true; - } - - - /********* - ** Private methods - *********/ - /// Get the correct reference to use for compatibility with the current platform. - /// The type reference to rewrite. - /// Whether to log a message. - private void ChangeTypeScope(TypeReference type, bool shouldLog) - { - // check skip conditions - if (type == null || type.FullName.StartsWith("System.")) - return; - - // get assembly - Assembly assembly; - if (!this.TypeAssemblies.TryGetValue(type.FullName, out assembly)) - return; - - // replace scope - AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly]; - if (shouldLog) - this.Monitor.Log($"redirecting {type.FullName} from {type.Scope.Name} to {assemblyRef.Name}", LogLevel.Trace); - type.Scope = assemblyRef; - } - - /// Get all methods in a module. - /// The module to search. - private IEnumerable GetMethods(ModuleDefinition module) - { - return ( - from type in module.GetTypes() - where type.HasMethods - from method in type.Methods - where method.HasBody - select method - ); - } - } -} diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs deleted file mode 100644 index 4c3b86fe..00000000 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.IO; -using StardewModdingAPI.AssemblyRewriters; - -namespace StardewModdingAPI.Framework.AssemblyRewriting -{ - /// Represents cached metadata for a rewritten assembly. - internal class CacheEntry - { - /********* - ** Accessors - *********/ - /// The MD5 hash for the original assembly. - public readonly string Hash; - - /// The SMAPI version used to rewrite the assembly. - public readonly string ApiVersion; - - /// The target platform. - public readonly Platform Platform; - - /// The value for the machine used to rewrite the assembly. - public readonly string MachineName; - - /// Whether to use the cached assembly instead of the original assembly. - public readonly bool UseCachedAssembly; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The MD5 hash for the original assembly. - /// The SMAPI version used to rewrite the assembly. - /// The target platform. - /// The value for the machine used to rewrite the assembly. - /// Whether to use the cached assembly instead of the original assembly. - public CacheEntry(string hash, string apiVersion, Platform platform, string machineName, bool useCachedAssembly) - { - this.Hash = hash; - this.ApiVersion = apiVersion; - this.Platform = platform; - this.MachineName = machineName; - this.UseCachedAssembly = useCachedAssembly; - } - - /// Get whether the cache entry is up-to-date for the given assembly hash. - /// The paths for the cached assembly. - /// The MD5 hash of the original assembly. - /// The current SMAPI version. - /// The target platform. - /// The value for the machine reading the assembly. - public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion, Platform platform, string machineName) - { - return hash == this.Hash - && this.ApiVersion == currentVersion.ToString() - && this.Platform == platform - && this.MachineName == machineName - && (!this.UseCachedAssembly || File.Exists(paths.Assembly)); - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs deleted file mode 100644 index 18861873..00000000 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace StardewModdingAPI.Framework.AssemblyRewriting -{ - /// Contains the paths for an assembly's cached data. - internal struct CachePaths - { - /********* - ** Accessors - *********/ - /// The directory path which contains the assembly. - public string Directory { get; } - - /// The file path of the assembly file. - public string Assembly { get; } - - /// The file path containing the assembly metadata. - public string Metadata { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The directory path which contains the assembly. - /// The file path of the assembly file. - /// The file path containing the assembly metadata. - public CachePaths(string directory, string assembly, string metadata) - { - this.Directory = directory; - this.Assembly = assembly; - this.Metadata = metadata; - } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs deleted file mode 100644 index 8f34bb20..00000000 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace StardewModdingAPI.Framework.AssemblyRewriting -{ - /// Metadata about a preprocessed assembly. - internal class RewriteResult - { - /********* - ** Accessors - *********/ - /// The original assembly path. - public readonly string OriginalAssemblyPath; - - /// The cache paths. - public readonly CachePaths CachePaths; - - /// The rewritten assembly bytes. - public readonly byte[] AssemblyBytes; - - /// The MD5 hash for the original assembly. - public readonly string Hash; - - /// Whether to use the cached assembly instead of the original assembly. - public readonly bool UseCachedAssembly; - - /// Whether this data is newer than the cache. - public readonly bool IsNewerThanCache; - - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// - /// The cache paths. - /// The rewritten assembly bytes. - /// The MD5 hash for the original assembly. - /// Whether to use the cached assembly instead of the original assembly. - /// Whether this data is newer than the cache. - public RewriteResult(string originalAssemblyPath, CachePaths cachePaths, byte[] assemblyBytes, string hash, bool useCachedAssembly, bool isNewerThanCache) - { - this.OriginalAssemblyPath = originalAssemblyPath; - this.CachePaths = cachePaths; - this.Hash = hash; - this.AssemblyBytes = assemblyBytes; - this.UseCachedAssembly = useCachedAssembly; - this.IsNewerThanCache = isNewerThanCache; - } - } -} diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs deleted file mode 100644 index e4760398..00000000 --- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; -using Mono.Cecil; -using Newtonsoft.Json; -using StardewModdingAPI.AssemblyRewriters; -using StardewModdingAPI.Framework.AssemblyRewriting; - -namespace StardewModdingAPI.Framework -{ - /// Preprocesses and loads mod assemblies. - internal class ModAssemblyLoader - { - /********* - ** Properties - *********/ - /// The name of the directory containing a mod's cached data. - private readonly string CacheDirName; - - /// Metadata for mapping assemblies to the current . - private readonly PlatformAssemblyMap AssemblyMap; - - /// Rewrites assembly types to match the current platform. - private readonly AssemblyTypeRewriter AssemblyTypeRewriter; - - /// Encapsulates monitoring and logging. - private readonly IMonitor Monitor; - - /// The current game platform. - private readonly Platform TargetPlatform; - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The name of the directory containing a mod's cached data. - /// The current game platform. - /// Encapsulates monitoring and logging. - public ModAssemblyLoader(string cacheDirName, Platform targetPlatform, IMonitor monitor) - { - this.CacheDirName = cacheDirName; - this.TargetPlatform = targetPlatform; - this.Monitor = monitor; - this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform); - this.AssemblyTypeRewriter = new AssemblyTypeRewriter(this.AssemblyMap, monitor); - } - - /// Preprocess an assembly unless the cache is up to date. - /// The assembly file path. - /// Returns the rewrite metadata for the preprocessed assembly. - public RewriteResult ProcessAssemblyUnlessCached(string assemblyPath) - { - // read assembly data - byte[] assemblyBytes = File.ReadAllBytes(assemblyPath); - string hash = string.Join("", MD5.Create().ComputeHash(assemblyBytes).Select(p => p.ToString("X2"))); - - // get cached result if current - CachePaths cachePaths = this.GetCachePaths(assemblyPath); - { - CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject(File.ReadAllText(cachePaths.Metadata)) : null; - if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion, this.TargetPlatform, Environment.MachineName)) - return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed - } - this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace); - - // rewrite assembly - AssemblyDefinition assembly; - using (Stream readStream = new MemoryStream(assemblyBytes)) - assembly = AssemblyDefinition.ReadAssembly(readStream); - bool modified = this.AssemblyTypeRewriter.RewriteAssembly(assembly); - using (MemoryStream outStream = new MemoryStream()) - { - assembly.Write(outStream); - byte[] outBytes = outStream.ToArray(); - return new RewriteResult(assemblyPath, cachePaths, outBytes, hash, useCachedAssembly: modified, isNewerThanCache: true); - } - } - - /// Write rewritten assembly metadata to the cache for a mod. - /// The rewrite results. - /// Whether to write all assemblies to the cache, even if they weren't modified. - /// There are no results to write, or the results are not all for the same directory. - public void WriteCache(IEnumerable results, bool forceCacheAssemblies) - { - results = results.ToArray(); - - // get cache directory - if (!results.Any()) - throw new InvalidOperationException("There are no assemblies to cache."); - if (results.Select(p => p.CachePaths.Directory).Distinct().Count() > 1) - throw new InvalidOperationException("The assemblies can't be cached together because they have different source directories."); - string cacheDir = results.Select(p => p.CachePaths.Directory).First(); - - // reset cache - if (Directory.Exists(cacheDir)) - Directory.Delete(cacheDir, recursive: true); - Directory.CreateDirectory(cacheDir); - - // cache all results - foreach (RewriteResult result in results) - { - CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), this.TargetPlatform, Environment.MachineName, forceCacheAssemblies || result.UseCachedAssembly); - File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry)); - if (forceCacheAssemblies || result.UseCachedAssembly) - File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes); - } - } - - /// Resolve an assembly from its name. - /// The assembly name. - /// - /// This implementation returns the first loaded assembly which matches the short form of - /// the assembly name, to resolve assembly resolution issues when rewriting - /// assemblies (especially with Mono). Since this is meant to be called on , - /// the implicit assumption is that loading the exact assembly failed. - /// - public Assembly ResolveAssembly(string name) - { - string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture) - return AppDomain.CurrentDomain - .GetAssemblies() - .FirstOrDefault(p => p.GetName().Name == shortName); - } - - - /********* - ** Private methods - *********/ - /// Get the cache details for an assembly. - /// The assembly file path. - private CachePaths GetCachePaths(string assemblyPath) - { - string fileName = Path.GetFileName(assemblyPath); - string dirPath = Path.Combine(Path.GetDirectoryName(assemblyPath), this.CacheDirName); - string cacheAssemblyPath = Path.Combine(dirPath, fileName); - string metadataPath = Path.Combine(dirPath, $"{fileName}.json"); - return new CachePaths(dirPath, cacheAssemblyPath, metadataPath); - } - } -} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 08b5c636..075d2de0 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -13,7 +13,6 @@ using Newtonsoft.Json; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.AssemblyRewriting; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Inheritance; using StardewValley; @@ -319,7 +318,7 @@ namespace StardewModdingAPI Program.Monitor.Log("Loading mods..."); // get assembly loader - ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CacheDirName, Program.TargetPlatform, Program.Monitor); + AssemblyLoader modAssemblyLoader = new AssemblyLoader(Program.TargetPlatform, Program.Monitor); AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); // get known incompatible mods @@ -458,50 +457,29 @@ namespace StardewModdingAPI } } - // preprocess mod assemblies for compatibility - var processedAssemblies = new List(); + // validate mod path to simplify errors + string assemblyPath = Path.Combine(directory, manifest.EntryDll); + if (!File.Exists(assemblyPath)) { - bool succeeded = true; - foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) - { - if (Path.GetFileName(assemblyPath).StartsWith(".")) - continue; // skip hidden files (e.g. Mac sometimes copies with "._" prefix). - - try - { - processedAssemblies.Add(modAssemblyLoader.ProcessAssemblyUnlessCached(assemblyPath)); - } - catch (Exception ex) - { - Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{Path.GetFileName(assemblyPath)}'.\n{ex.GetLogSummary()}", LogLevel.Error); - succeeded = false; - break; - } - } - if (!succeeded) - continue; + Program.Monitor.Log($"{errorPrefix}: the entry DLL '{manifest.EntryDll}' does not exist.", LogLevel.Error); + continue; } - bool forceUseCachedAssembly = processedAssemblies.Any(p => p.UseCachedAssembly); // make sure DLLs are kept together for dependency resolution - if (processedAssemblies.Any(p => p.IsNewerThanCache)) - modAssemblyLoader.WriteCache(processedAssemblies, forceUseCachedAssembly); - // get entry assembly path - string mainAssemblyPath; + // preprocess & load mod assembly + Assembly modAssembly; + try { - RewriteResult mainProcessedAssembly = processedAssemblies.FirstOrDefault(p => p.OriginalAssemblyPath == Path.Combine(directory, manifest.EntryDll)); - if (mainProcessedAssembly == null) - { - Program.Monitor.Log($"{errorPrefix}: the specified mod DLL does not exist.", LogLevel.Error); - continue; - } - mainAssemblyPath = forceUseCachedAssembly ? mainProcessedAssembly.CachePaths.Assembly : mainProcessedAssembly.OriginalAssemblyPath; + modAssembly = modAssemblyLoader.Load(assemblyPath); + } + catch (Exception ex) + { + Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{manifest.EntryDll}'.\n{ex.GetLogSummary()}", LogLevel.Error); + continue; } - // load entry assembly - Assembly modAssembly; + // validate assembly try { - modAssembly = Assembly.UnsafeLoadFrom(mainAssemblyPath); // unsafe load allows downloaded DLLs if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); @@ -510,11 +488,11 @@ namespace StardewModdingAPI } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: an error occurred while optimising the target DLL.\n{ex.GetLogSummary()}", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: an error occurred while reading the mod DLL.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } - // get mod instance + // initialise mod Mod mod; try { diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index d56b6866..337929e2 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -148,6 +148,8 @@ + + @@ -156,15 +158,11 @@ - - - - - + @@ -268,4 +266,4 @@ - + \ No newline at end of file -- cgit From 4fff06cce19fcc5f75da949f37cff4ca3ee46b44 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Feb 2017 01:40:47 -0500 Subject: fix documentation issues --- src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs | 2 -- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs index f2826080..fce2b187 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/PlatformAssemblyMap.cs @@ -20,8 +20,6 @@ namespace StardewModdingAPI.AssemblyRewriters /// The short assembly names to remove as assembly reference, and replace with the . These should be short names (like "Stardew Valley"). public readonly string[] RemoveNames; - /// The assembly filenames to target. Equivalent types should be rewritten to use these assemblies. - /**** ** Metadata ****/ diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 37f2764a..8b2f29e1 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -53,7 +53,7 @@ namespace StardewModdingAPI.Framework } } - /// Preprocess and load an assembly, writing modified assemblies to the cache folder if needed. + /// Preprocess and load an assembly. /// The assembly file path. /// Returns the rewrite metadata for the preprocessed assembly. public Assembly Load(string assemblyPath) -- cgit From 5e68400c43000f07a29103cacccacde72b0cce2e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Feb 2017 20:12:42 -0500 Subject: only read assembly from memory if it was rewritten (#229) This fixes an issue where you can't debug into mod code because SMAPI isn't loading the actual DLL. --- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 8b2f29e1..123211b9 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -72,14 +72,19 @@ namespace StardewModdingAPI.Framework Assembly lastAssembly = null; foreach (AssemblyParseResult assembly in assemblies) { - this.Monitor.Log($"Loading {assembly.File.FullName}...", LogLevel.Trace); - this.RewriteAssembly(assembly.Definition); - using (MemoryStream outStream = new MemoryStream()) + this.Monitor.Log($"Loading {assembly.File.Name}...", LogLevel.Trace); + bool changed = this.RewriteAssembly(assembly.Definition); + if (changed) { - assembly.Definition.Write(outStream); - byte[] bytes = outStream.ToArray(); - lastAssembly = Assembly.Load(bytes); + using (MemoryStream outStream = new MemoryStream()) + { + assembly.Definition.Write(outStream); + byte[] bytes = outStream.ToArray(); + lastAssembly = Assembly.Load(bytes); + } } + else + lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName); } // last assembly loaded is the root -- cgit From 0b8396cc53ff28d3808e0c6d3fea693775d2ab81 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 3 Feb 2017 22:26:48 -0500 Subject: rm cruft --- src/StardewModdingAPI/Program.cs | 7 ------- 1 file changed, 7 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 075d2de0..45bf1238 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -40,9 +40,6 @@ namespace StardewModdingAPI /// The full path to the folder containing mods. private static readonly string ModPath = Path.Combine(Constants.ExecutionPath, "Mods"); - /// The name of the folder containing a mod's cached assembly data. - private static readonly string CacheDirName = ".cache"; - /// The log file to which to write messages. private static readonly LogFileManager LogFile = new LogFileManager(Constants.LogPath); @@ -341,10 +338,6 @@ namespace StardewModdingAPI { string directoryName = new DirectoryInfo(directory).Name; - // ignore internal directory - if (directoryName == ".cache") - continue; - // check for cancellation if (Program.CancellationTokenSource.IsCancellationRequested) { -- cgit From 57a99803f05b9f13f2adf2f6cf11c074f094851a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Feb 2017 15:27:13 -0500 Subject: update deprecation level for Extensions class --- release-notes.md | 1 + src/StardewModdingAPI/Extensions.cs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/release-notes.md b/release-notes.md index 18d67f1b..03c5f1f3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -13,6 +13,7 @@ For mod developers: * **Breaking change:** `Assembly.GetExecutingAssembly().Location` will no longer reliably return a valid path, because mod assemblies will be loaded from memory when rewritten for compatibility. (It's been deprecated since SMAPI 1.3.) +* Increased deprecation level for `Extensions` to _pending removal_. For SMAPI developers: * Rewrote assembly loading from the ground up. The new implementation... diff --git a/src/StardewModdingAPI/Extensions.cs b/src/StardewModdingAPI/Extensions.cs index 1229ca97..0e9dbbf7 100644 --- a/src/StardewModdingAPI/Extensions.cs +++ b/src/StardewModdingAPI/Extensions.cs @@ -27,7 +27,7 @@ namespace StardewModdingAPI { get { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Random)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Random)}", "1.0", DeprecationLevel.PendingRemoval); return Extensions._random; } } @@ -40,7 +40,7 @@ namespace StardewModdingAPI /// The key to check. public static bool IsKeyDown(this Keys key) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsKeyDown)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsKeyDown)}", "1.0", DeprecationLevel.PendingRemoval); return Keyboard.GetState().IsKeyDown(key); } @@ -48,7 +48,7 @@ namespace StardewModdingAPI /// Get a random color. public static Color RandomColour() { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RandomColour)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RandomColour)}", "1.0", DeprecationLevel.PendingRemoval); return new Color(Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255), Extensions.Random.Next(0, 255)); } @@ -69,7 +69,7 @@ namespace StardewModdingAPI /// The value separator. public static string ToSingular(this IEnumerable ienum, string split = ", ") { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.ToSingular)}", "1.0", DeprecationLevel.PendingRemoval); //Apparently Keys[] won't split normally :l if (typeof(T) == typeof(Keys)) @@ -83,7 +83,7 @@ namespace StardewModdingAPI /// The value. public static bool IsInt32(this object o) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsInt32)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsInt32)}", "1.0", DeprecationLevel.PendingRemoval); int i; return int.TryParse(o.ToString(), out i); @@ -93,7 +93,7 @@ namespace StardewModdingAPI /// The value. public static int AsInt32(this object o) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsInt32)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsInt32)}", "1.0", DeprecationLevel.PendingRemoval); return int.Parse(o.ToString()); } @@ -102,7 +102,7 @@ namespace StardewModdingAPI /// The value. public static bool IsBool(this object o) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsBool)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.IsBool)}", "1.0", DeprecationLevel.PendingRemoval); bool b; return bool.TryParse(o.ToString(), out b); @@ -112,7 +112,7 @@ namespace StardewModdingAPI /// The value. public static bool AsBool(this object o) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsBool)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.AsBool)}", "1.0", DeprecationLevel.PendingRemoval); return bool.Parse(o.ToString()); } @@ -121,7 +121,7 @@ namespace StardewModdingAPI /// The values to hash. public static int GetHash(this IEnumerable enumerable) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetHash)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetHash)}", "1.0", DeprecationLevel.PendingRemoval); var hash = 0; foreach (var v in enumerable) @@ -134,7 +134,7 @@ namespace StardewModdingAPI /// The value. public static T Cast(this object o) where T : class { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Cast)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.Cast)}", "1.0", DeprecationLevel.PendingRemoval); return o as T; } @@ -143,7 +143,7 @@ namespace StardewModdingAPI /// The object to scan. public static FieldInfo[] GetPrivateFields(this object o) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetPrivateFields)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetPrivateFields)}", "1.0", DeprecationLevel.PendingRemoval); return o.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); } @@ -152,7 +152,7 @@ namespace StardewModdingAPI /// The name of the field to find. public static FieldInfo GetBaseFieldInfo(this Type t, string name) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); return t.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); } @@ -162,7 +162,7 @@ namespace StardewModdingAPI /// The name of the field to find. public static T GetBaseFieldValue(this Type t, object o, string name) where T : class { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.GetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); return t.GetBaseFieldInfo(name).GetValue(o) as T; } @@ -173,7 +173,7 @@ namespace StardewModdingAPI /// The value to set. public static void SetBaseFieldValue(this Type t, object o, string name, object newValue) where T : class { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.SetBaseFieldValue)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.SetBaseFieldValue)}", "1.0", DeprecationLevel.PendingRemoval); t.GetBaseFieldInfo(name).SetValue(o, newValue as T); } @@ -181,7 +181,7 @@ namespace StardewModdingAPI /// The string to copy. public static string RemoveNumerics(this string st) { - Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RemoveNumerics)}", "1.0", DeprecationLevel.Info); + Program.DeprecationManager.Warn($"{nameof(Extensions)}.{nameof(Extensions.RemoveNumerics)}", "1.0", DeprecationLevel.PendingRemoval); var s = st; foreach (var c in s) { -- cgit From 3919ab7a4aed7acd579e471f5660df5fbc890ae2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Feb 2017 15:30:26 -0500 Subject: update for 1.8 release --- release-notes.md | 11 ++++++----- src/GlobalAssemblyInfo.cs | 4 ++-- src/StardewModdingAPI/Constants.cs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/release-notes.md b/release-notes.md index 03c5f1f3..1ff868a4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,16 +4,17 @@ See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). For players: -* Mods will no longer generate `.cache` subfolders. +* Mods no longer generate `.cache` subfolders. * Fixed multiple issues where mods failed during assembly loading. * Tweaked install package to reduce confusion. For mod developers: -* You can now create a `SemanticVersion` from a version string. -* **Breaking change:** `Assembly.GetExecutingAssembly().Location` will no longer reliably - return a valid path, because mod assemblies will be loaded from memory when rewritten for - compatibility. (It's been deprecated since SMAPI 1.3.) +* The `SemanticVersion` constructor now accepts a string version. * Increased deprecation level for `Extensions` to _pending removal_. +* **Warning:** `Assembly.GetExecutingAssembly().Location` will no longer reliably + return a valid path, because mod assemblies are loaded from memory when rewritten for + compatibility. This approach has been discouraged since SMAPI 1.3; use `helper.DirectoryPath` + instead. For SMAPI developers: * Rewrote assembly loading from the ground up. The new implementation... diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs index 29e5dae7..b591153a 100644 --- a/src/GlobalAssemblyInfo.cs +++ b/src/GlobalAssemblyInfo.cs @@ -2,5 +2,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.8.0.0")] +[assembly: AssemblyFileVersion("1.8.0.0")] \ No newline at end of file diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index df527dfe..a62a0d58 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI public static readonly Version Version = (Version)Constants.ApiVersion; /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion => new Version(1, 7, 0, null, suppressDeprecationWarning: true); + public static ISemanticVersion ApiVersion => new Version(1, 8, 0, null, suppressDeprecationWarning: true); /// The minimum supported version of Stardew Valley. public const string MinimumGameVersion = "1.1"; -- cgit