diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-01 18:16:09 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-01 18:16:09 -0400 |
commit | c8ad50dad1d706a1901798f9396f6becfea36c0e (patch) | |
tree | 28bd818a5db39ec5ece1bd141a28de955950463b /src/SMAPI/Framework/ModLoading | |
parent | 451b70953ff4c0b1b27ae0de203ad99379b45b2a (diff) | |
parent | f78093bdb58d477b400cde3f19b70ffd6ddf833d (diff) | |
download | SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.gz SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.bz2 SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/ModLoading')
29 files changed, 217 insertions, 200 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 8e2f5ef3..b3378ad1 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -36,6 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Resolve an assembly reference.</summary> /// <param name="name">The assembly name.</param> + /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception> public override AssemblyDefinition Resolve(AssemblyNameReference name) { return this.ResolveName(name.Name) ?? base.Resolve(name); @@ -44,6 +45,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Resolve an assembly reference.</summary> /// <param name="name">The assembly name.</param> /// <param name="parameters">The assembly reader parameters.</param> + /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception> public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { return this.ResolveName(name.Name) ?? base.Resolve(name, parameters); @@ -55,9 +57,9 @@ namespace StardewModdingAPI.Framework.ModLoading *********/ /// <summary>Resolve a known assembly definition based on its short or full name.</summary> /// <param name="name">The assembly's short or full name.</param> - private AssemblyDefinition ResolveName(string name) + private AssemblyDefinition? ResolveName(string name) { - return this.Lookup.TryGetValue(name, out AssemblyDefinition match) + return this.Lookup.TryGetValue(name, out AssemblyDefinition? match) ? match : null; } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index cb5fa2ae..72b547b1 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -36,13 +36,13 @@ namespace StardewModdingAPI.Framework.ModLoading private readonly AssemblyDefinitionResolver AssemblyDefinitionResolver; /// <summary>Provides assembly symbol readers for Mono.Cecil.</summary> - private readonly SymbolReaderProvider SymbolReaderProvider = new SymbolReaderProvider(); + private readonly SymbolReaderProvider SymbolReaderProvider = new(); /// <summary>Provides assembly symbol writers for Mono.Cecil.</summary> - private readonly SymbolWriterProvider SymbolWriterProvider = new SymbolWriterProvider(); + private readonly SymbolWriterProvider SymbolWriterProvider = new(); /// <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(); /// <summary>Whether to rewrite mods for compatibility.</summary> private readonly bool RewriteMods; @@ -94,7 +94,12 @@ namespace StardewModdingAPI.Framework.ModLoading // get referenced local assemblies AssemblyParseResult[] assemblies; { - HashSet<string> visitedAssemblyNames = new HashSet<string>(AppDomain.CurrentDomain.GetAssemblies().Select(p => p.GetName().Name)); // don't try loading assemblies that are already loaded + HashSet<string> visitedAssemblyNames = new HashSet<string>( // don't try loading assemblies that are already loaded + from assembly in AppDomain.CurrentDomain.GetAssemblies() + let name = assembly.GetName().Name + where name != null + select name + ); assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), visitedAssemblyNames, this.AssemblyDefinitionResolver).ToArray(); } @@ -111,11 +116,11 @@ namespace StardewModdingAPI.Framework.ModLoading // rewrite & load assemblies in leaf-to-root order bool oneAssembly = assemblies.Length == 1; - Assembly lastAssembly = null; + Assembly? lastAssembly = null; HashSet<string> loggedMessages = new HashSet<string>(); foreach (AssemblyParseResult assembly in assemblies) { - if (assembly.Status == AssemblyLoadStatus.AlreadyLoaded) + if (!assembly.HasDefinition) continue; // rewrite assembly @@ -138,11 +143,11 @@ namespace StardewModdingAPI.Framework.ModLoading if (changed) { if (!oneAssembly) - this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace); + this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)..."); // load assembly - using MemoryStream outAssemblyStream = new MemoryStream(); - using MemoryStream outSymbolStream = new MemoryStream(); + using MemoryStream outAssemblyStream = new(); + using MemoryStream outSymbolStream = new(); assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider }); byte[] bytes = outAssemblyStream.ToArray(); lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray()); @@ -150,7 +155,7 @@ namespace StardewModdingAPI.Framework.ModLoading else { if (!oneAssembly) - this.Monitor.Log($" Loading {assembly.File.Name}...", LogLevel.Trace); + this.Monitor.Log($" Loading {assembly.File.Name}..."); lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName); } @@ -163,7 +168,7 @@ namespace StardewModdingAPI.Framework.ModLoading throw new IncompatibleInstructionException(); // last assembly loaded is the root - return lastAssembly; + return lastAssembly!; } /// <summary>Get whether an assembly is loaded.</summary> @@ -172,7 +177,8 @@ namespace StardewModdingAPI.Framework.ModLoading { try { - return this.AssemblyDefinitionResolver.Resolve(reference) != null; + _ = this.AssemblyDefinitionResolver.Resolve(reference); + return true; } catch (AssemblyResolutionException) { @@ -188,7 +194,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// assemblies (especially with Mono). Since this is meant to be called on <see cref="AppDomain.AssemblyResolve"/>, /// the implicit assumption is that loading the exact assembly failed. /// </remarks> - public static Assembly ResolveAssembly(string name) + public static Assembly? ResolveAssembly(string name) { string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture) return AppDomain.CurrentDomain @@ -210,7 +216,8 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Track an object for disposal as part of the assembly loader.</summary> /// <typeparam name="T">The instance type.</typeparam> /// <param name="instance">The disposable instance.</param> - private T TrackForDisposal<T>(T instance) where T : IDisposable + private T TrackForDisposal<T>(T instance) + where T : IDisposable { this.Disposables.Add(instance); return instance; @@ -241,7 +248,7 @@ namespace StardewModdingAPI.Framework.ModLoading try { // read assembly with symbols - FileInfo symbolsFile = new FileInfo(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb"); + FileInfo symbolsFile = new(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb"); if (symbolsFile.Exists) this.SymbolReaderProvider.TryAddSymbolData(file.Name, () => this.TrackForDisposal(symbolsFile.OpenRead())); assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true, ReadSymbols = true, SymbolReaderProvider = this.SymbolReaderProvider })); @@ -266,7 +273,7 @@ namespace StardewModdingAPI.Framework.ModLoading // yield referenced assemblies foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences) { - FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); + FileInfo dependencyFile = new(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyNames, assemblyResolver)) yield return result; } @@ -319,9 +326,9 @@ namespace StardewModdingAPI.Framework.ModLoading // rewrite types using custom attributes foreach (TypeDefinition type in module.GetTypes()) { - foreach (var attr in type.CustomAttributes) + foreach (CustomAttribute attr in type.CustomAttributes) { - foreach (var conField in attr.ConstructorArguments) + foreach (CustomAttributeArgument conField in attr.ConstructorArguments) { if (conField.Value is TypeReference typeRef) this.ChangeTypeScope(typeRef); @@ -333,7 +340,7 @@ namespace StardewModdingAPI.Framework.ModLoading // find or rewrite code IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray(); - RecursiveRewriter rewriter = new RecursiveRewriter( + RecursiveRewriter rewriter = new( module: module, rewriteModule: curModule => { @@ -380,7 +387,7 @@ namespace StardewModdingAPI.Framework.ModLoading { // get message template // ($phrase is replaced with the noun phrase or messages) - string template = null; + string? template = null; switch (result) { case InstructionHandleResult.Rewritten: @@ -439,20 +446,20 @@ namespace StardewModdingAPI.Framework.ModLoading // format messages string phrase = handler.Phrases.Any() ? string.Join(", ", handler.Phrases) - : handler.DefaultPhrase ?? handler.GetType().Name; + : handler.DefaultPhrase; this.Monitor.LogOnce(loggedMessages, template.Replace("$phrase", phrase)); } /// <summary>Get the correct reference to use for compatibility with the current platform.</summary> /// <param name="type">The type reference to rewrite.</param> - private void ChangeTypeScope(TypeReference type) + private void ChangeTypeScope(TypeReference? type) { // check skip conditions if (type == null || type.FullName.StartsWith("System.")) return; // get assembly - if (!this.TypeAssemblies.TryGetValue(type.FullName, out Assembly assembly)) + if (!this.TypeAssemblies.TryGetValue(type.FullName, out Assembly? assembly)) return; // replace scope diff --git a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs index b56a776c..b133f8d6 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,3 +1,5 @@ +using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Mono.Cecil; @@ -13,11 +15,15 @@ namespace StardewModdingAPI.Framework.ModLoading public readonly FileInfo File; /// <summary>The assembly definition.</summary> - public readonly AssemblyDefinition Definition; + public readonly AssemblyDefinition? Definition; /// <summary>The result of the assembly load.</summary> public AssemblyLoadStatus Status; + /// <summary>Whether the <see cref="Definition"/> is loaded and ready (i.e. the <see cref="Status"/> is not <see cref="AssemblyLoadStatus.AlreadyLoaded"/> or <see cref="AssemblyLoadStatus.Failed"/>).</summary> + [MemberNotNullWhen(true, nameof(AssemblyParseResult.Definition))] + public bool HasDefinition => this.Status == AssemblyLoadStatus.Okay; + /********* ** Public methods @@ -26,11 +32,14 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="file">The original assembly file.</param> /// <param name="assembly">The assembly definition.</param> /// <param name="status">The result of the assembly load.</param> - public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly, AssemblyLoadStatus status) + public AssemblyParseResult(FileInfo file, AssemblyDefinition? assembly, AssemblyLoadStatus status) { this.File = file; this.Definition = assembly; this.Status = status; + + if (status == AssemblyLoadStatus.Okay && assembly == null) + throw new InvalidOperationException($"Invalid assembly parse result: load status {status} with a null assembly."); } } } diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 124951a5..f5d449c5 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -55,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.MethodNames.Any()) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name)) { string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1]; diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 68415123..7fe4abec 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.FieldNames.Any()) { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && fieldRef.DeclaringType.FullName == this.FullTypeName && this.FieldNames.Contains(fieldRef.Name)) { this.FieldNames.Remove(fieldRef.Name); diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index d2340f01..e8fdc8c7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="instruction">The IL instruction.</param> protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index 99344848..2af76f55 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="instruction">The IL instruction.</param> protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 8c1cae2b..f34542c3 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -32,11 +33,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { // get target field - FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); + FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (targetField == null) return false; @@ -49,16 +50,16 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodReference = RewriteHelper.AsMethodReference(instruction); if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType)) { // get potential targets - MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); + MethodDefinition[]? candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); if (candidateMethods == null || !candidateMethods.Any()) return false; // compare return types - MethodDefinition methodDef = methodReference.Resolve(); + MethodDefinition? methodDef = methodReference.Resolve(); if (methodDef == null) return false; // validated by ReferenceToMissingMemberFinder @@ -78,7 +79,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index d305daf4..fae7fb12 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -31,10 +32,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { - FieldDefinition target = fieldRef.Resolve(); + FieldDefinition? target = fieldRef.Resolve(); if (target == null || target.HasConstant) { this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)"); @@ -43,10 +44,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && this.ShouldValidate(methodRef.DeclaringType) && !this.IsUnsupported(methodRef)) { - MethodDefinition target = methodRef.Resolve(); + MethodDefinition? target = methodRef.Resolve(); if (target == null) { string phrase; @@ -71,7 +72,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 24ab2eca..17acbf9a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="assemblyName">The full assembly name to which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : base(defaultPhrase: $"{assemblyName} assembly") { this.AssemblyName = assemblyName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 260a8df8..77762f41 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="fullTypeNames">The full type names to match.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : base(defaultPhrase: $"{string.Join(", ", fullTypeNames)} type{(fullTypeNames.Length != 1 ? "s" : "")}") // default phrase should never be used { this.FullTypeNames = new HashSet<string>(fullTypeNames); @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="fullTypeName">The full type name to match.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : this(new[] { fullTypeName }, result, shouldIgnore) { } /// <inheritdoc /> diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index d5d1b38e..865bf076 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -57,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <param name="flag">The result flag to set.</param> /// <param name="resultMessage">The result message to add.</param> /// <returns>Returns true for convenience.</returns> - protected bool MarkFlag(InstructionHandleResult flag, string resultMessage = null) + protected bool MarkFlag(InstructionHandleResult flag, string? resultMessage = null) { this.Flags.Add(flag); if (resultMessage != null) diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 4f14a579..55369602 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -8,6 +9,7 @@ using Mono.Collections.Generic; namespace StardewModdingAPI.Framework.ModLoading.Framework { /// <summary>Handles recursively rewriting loaded assembly code.</summary> + [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "Rewrite callbacks are invoked immediately.")] internal class RecursiveRewriter { /********* @@ -75,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework { changed |= this.RewriteModuleImpl(this.Module); - foreach (var type in types) + foreach (TypeDefinition type in types) changed |= this.RewriteTypeDefinition(type); } catch (Exception ex) @@ -127,9 +129,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework ILProcessor cil = method.Body.GetILProcessor(); Collection<Instruction> instructions = cil.Body.Instructions; bool addedInstructions = false; + // ReSharper disable once ForCanBeConvertedToForeach -- deliberate to allow changing the collection for (int i = 0; i < instructions.Count; i++) { - var instruction = instructions[i]; + Instruction instruction = instructions[i]; if (instruction.OpCode.Code == Code.Nop) continue; @@ -172,7 +175,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool rewritten = false; // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { rewritten |= this.RewriteTypeReference(fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); @@ -180,7 +183,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null) this.RewriteMethodReference(methodRef); @@ -210,7 +213,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework }); rewritten |= this.RewriteTypeReference(methodRef.ReturnType, newType => methodRef.ReturnType = newType); - foreach (var parameter in methodRef.Parameters) + foreach (ParameterDefinition parameter in methodRef.Parameters) rewritten |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); if (methodRef is GenericInstanceMethod genericRef) @@ -262,7 +265,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool curChanged = false; // attribute type - TypeReference newAttrType = null; + TypeReference? newAttrType = null; rewritten |= this.RewriteTypeReference(attribute.AttributeType, newType => { newAttrType = newType; @@ -287,9 +290,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework if (curChanged) { // get constructor - MethodDefinition constructor = (newAttrType ?? attribute.AttributeType) + MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType) .Resolve() - .Methods + ?.Methods .Where(method => method.IsConstructor) .FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor)); if (constructor == null) @@ -299,9 +302,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework var newAttr = new CustomAttribute(this.Module.ImportReference(constructor)); for (int i = 0; i < argTypes.Length; i++) newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value)); - foreach (var prop in attribute.Properties) + foreach (CustomAttributeNamedArgument prop in attribute.Properties) newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument)); - foreach (var field in attribute.Fields) + foreach (CustomAttributeNamedArgument field in attribute.Fields) newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument)); // swap attribute diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index d7cb2471..15f71251 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework *********/ /// <summary>Get the field reference from an instruction if it matches.</summary> /// <param name="instruction">The IL instruction.</param> - public static FieldReference AsFieldReference(Instruction instruction) + public static FieldReference? AsFieldReference(Instruction instruction) { return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld ? (FieldReference)instruction.Operand @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <summary>Get the method reference from an instruction if it matches.</summary> /// <param name="instruction">The IL instruction.</param> - public static MethodReference AsMethodReference(Instruction instruction) + public static MethodReference? AsMethodReference(Instruction instruction) { return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj ? (MethodReference)instruction.Operand @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <summary>Get the CIL instruction to load a value onto the stack.</summary> /// <param name="rawValue">The constant value to inject.</param> /// <returns>Returns the instruction, or <c>null</c> if the value type isn't supported.</returns> - public static Instruction GetLoadValueInstruction(object rawValue) + public static Instruction? GetLoadValueInstruction(object? rawValue) { return rawValue switch { @@ -149,7 +149,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <param name="typeA">The type ID to compare.</param> /// <param name="typeB">The other type ID to compare.</param> /// <returns>true if the type IDs look like the same type, false if not.</returns> - public static bool LooksLikeSameType(TypeReference typeA, TypeReference typeB) + public static bool LooksLikeSameType(TypeReference? typeA, TypeReference? typeB) { return RewriteHelper.TypeDefinitionComparer.Equals(typeA, typeB); } diff --git a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs index 075e237a..b53a9886 100644 --- a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs +++ b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Construct an instance.</summary> /// <param name="message">The error message.</param> /// <param name="ex">The underlying exception, if any.</param> - public InvalidModStateException(string message, Exception ex = null) + public InvalidModStateException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 9e6bc61f..fe54634b 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using StardewModdingAPI.Framework.ModHelpers; @@ -42,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading public IManifest Manifest { get; } /// <inheritdoc /> - public ModDataRecordVersionedFields DataRecord { get; } + public ModDataRecordVersionedFields? DataRecord { get; } /// <inheritdoc /> public ModMetadataStatus Status { get; private set; } @@ -54,33 +55,35 @@ namespace StardewModdingAPI.Framework.ModLoading public ModWarning Warnings => this.ActualWarnings & ~(this.DataRecord?.DataRecord.SuppressWarnings ?? ModWarning.None); /// <inheritdoc /> - public string Error { get; private set; } + public string? Error { get; private set; } /// <inheritdoc /> - public string ErrorDetails { get; private set; } + public string? ErrorDetails { get; private set; } /// <inheritdoc /> public bool IsIgnored { get; } /// <inheritdoc /> - public IMod Mod { get; private set; } + public IMod? Mod { get; private set; } /// <inheritdoc /> - public IContentPack ContentPack { get; private set; } + public IContentPack? ContentPack { get; private set; } /// <inheritdoc /> - public TranslationHelper Translations { get; private set; } + public TranslationHelper? Translations { get; private set; } /// <inheritdoc /> - public IMonitor Monitor { get; private set; } + public IMonitor? Monitor { get; private set; } /// <inheritdoc /> - public object Api { get; private set; } + public object? Api { get; private set; } /// <inheritdoc /> - public ModEntryModel UpdateCheckData { get; private set; } + public ModEntryModel? UpdateCheckData { get; private set; } /// <inheritdoc /> + [MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))] + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")] public bool IsContentPack => this.Manifest?.ContentPackFor != null; /// <summary>The fake content packs created by this mod, if any.</summary> @@ -97,13 +100,13 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="manifest">The mod manifest.</param> /// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param> /// <param name="isIgnored">Whether the mod folder should be ignored. This should be <c>true</c> if it was found within a folder whose name starts with a dot.</param> - public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored) + public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest? manifest, ModDataRecordVersionedFields? dataRecord, bool isIgnored) { this.DisplayName = displayName; this.DirectoryPath = directoryPath; this.RootPath = rootPath; this.RelativeDirectoryPath = PathUtilities.GetRelativePath(this.RootPath, this.DirectoryPath); - this.Manifest = manifest; + this.Manifest = manifest!; // manifest may be null in low-level SMAPI code, but won't be null once it's received by mods via IModInfo this.DataRecord = dataRecord; this.IsIgnored = isIgnored; @@ -119,7 +122,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null) + public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null) { this.Status = status; this.FailReason = reason; @@ -160,7 +163,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public IModMetadata SetApi(object api) + public IModMetadata SetApi(object? api) { this.Api = api; return this; @@ -174,6 +177,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> + [MemberNotNullWhen(true, nameof(IModInfo.Manifest))] public bool HasManifest() { return this.Manifest != null; @@ -188,7 +192,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public bool HasID(string id) + public bool HasID(string? id) { return this.HasID() @@ -243,7 +247,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <inheritdoc /> public string GetRelativePathWithRoot() { - string rootFolderName = Path.GetFileName(this.RootPath) ?? ""; + string rootFolderName = Path.GetFileName(this.RootPath); return Path.Combine(rootFolderName, this.RelativeDirectoryPath); } @@ -252,7 +256,7 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (var reference in this.FakeContentPacks.ToArray()) { - if (!reference.TryGetTarget(out ContentPack pack)) + if (!reference.TryGetTarget(out ContentPack? pack)) { this.FakeContentPacks.Remove(reference); continue; diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 4b05d1e5..74e7cb32 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using StardewModdingAPI.Toolkit; @@ -26,17 +27,14 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { - Manifest manifest = folder.Manifest; + Manifest? manifest = folder.Manifest; // parse internal data record (if any) - ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); + ModDataRecordVersionedFields? dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults - if (manifest != null && dataRecord != null) - { - if (dataRecord.UpdateKey != null) - manifest.UpdateKeys = new[] { dataRecord.UpdateKey }; - } + if (manifest != null && dataRecord?.UpdateKey is not null) + manifest.OverrideUpdateKeys(dataRecord.UpdateKey); // build metadata bool shouldIgnore = folder.Type == ModType.Ignored; @@ -44,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading ? ModMetadataStatus.Found : ModMetadataStatus.Failed; - var metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); + IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); else @@ -58,7 +56,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="mods">The mod manifests to validate.</param> /// <param name="apiVersion">The current SMAPI version.</param> /// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param> - public void ValidateManifests(IEnumerable<IModMetadata> mods, ISemanticVersion apiVersion, Func<string, string> getUpdateUrl) + /// <param name="validateFilesExist">Whether to validate that files referenced in the manifest (like <see cref="IManifest.EntryDll"/>) exist on disk. This can be disabled to only validate the manifest itself.</param> + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifest values may be null before they're validated.")] + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Manifest values may be null before they're validated.")] + public void ValidateManifests(IEnumerable<IModMetadata> mods, ISemanticVersion apiVersion, Func<string, string?> getUpdateUrl, bool validateFilesExist = true) { mods = mods.ToArray(); @@ -85,7 +86,7 @@ namespace StardewModdingAPI.Framework.ModLoading List<string> updateUrls = new List<string>(); foreach (UpdateKey key in mod.GetUpdateKeys(validOnly: true)) { - string url = getUpdateUrl(key.ToString()); + string? url = getUpdateUrl(key.ToString()); if (url != null) updateUrls.Add(url); } @@ -95,7 +96,7 @@ namespace StardewModdingAPI.Framework.ModLoading // build error string error = $"{reasonPhrase}. Please check for a "; - if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version.Equals(mod.DataRecord.StatusUpperVersion)) + if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version?.Equals(mod.DataRecord.StatusUpperVersion) == true) error += "newer version"; else error += $"version newer than {mod.DataRecord.StatusUpperVersion}"; @@ -134,25 +135,21 @@ namespace StardewModdingAPI.Framework.ModLoading if (hasDll) { // invalid filename format - if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any()) + if (mod.Manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any()) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field."); continue; } - // invalid path - if (!File.Exists(Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll))) - { - mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); - continue; - } - - // invalid capitalization - string actualFilename = new DirectoryInfo(mod.DirectoryPath).GetFiles(mod.Manifest.EntryDll).FirstOrDefault()?.Name; - if (actualFilename != mod.Manifest.EntryDll) + // file doesn't exist + if (validateFilesExist) { - mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {nameof(IManifest.EntryDll)} value '{mod.Manifest.EntryDll}' doesn't match the actual file capitalization '{actualFilename}'. The capitalization must match for crossplatform compatibility."); - continue; + string fileName = CaseInsensitivePathLookup.GetCachedFor(mod.DirectoryPath).GetFilePath(mod.Manifest.EntryDll!); + if (!File.Exists(Path.Combine(mod.DirectoryPath, fileName))) + { + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); + continue; + } } } @@ -160,7 +157,7 @@ namespace StardewModdingAPI.Framework.ModLoading else { // invalid content pack ID - if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor.UniqueID)) + if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor!.UniqueID)) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field."); continue; @@ -191,7 +188,7 @@ namespace StardewModdingAPI.Framework.ModLoading mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); // validate dependencies - foreach (var dependency in mod.Manifest.Dependencies) + foreach (IManifestDependency? dependency in mod.Manifest.Dependencies) { // null dependency if (dependency == null) @@ -240,7 +237,7 @@ namespace StardewModdingAPI.Framework.ModLoading // initialize metadata mods = mods.ToArray(); var sortedMods = new Stack<IModMetadata>(); - var states = mods.ToDictionary(mod => mod, mod => ModDependencyStatus.Queued); + var states = mods.ToDictionary(mod => mod, _ => ModDependencyStatus.Queued); // handle failed mods foreach (IModMetadata mod in mods.Where(m => m.Status == ModMetadataStatus.Failed)) @@ -329,8 +326,11 @@ namespace StardewModdingAPI.Framework.ModLoading string[] failedLabels = ( from entry in dependencies - where entry.Mod != null && entry.MinVersion != null && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) - select $"{entry.Mod.DisplayName} (needs {entry.MinVersion} or later)" + where + entry.Mod != null + && entry.MinVersion != null + && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) + select $"{entry.Mod!.DisplayName} (needs {entry.MinVersion} or later)" ) .ToArray(); if (failedLabels.Any()) @@ -346,16 +346,14 @@ namespace StardewModdingAPI.Framework.ModLoading states[mod] = ModDependencyStatus.Checking; // recursively sort dependencies - foreach (var dependency in dependencies) + foreach (ModDependency dependency in dependencies) { - IModMetadata requiredMod = dependency.Mod; - var subchain = new List<IModMetadata>(currentChain) { mod }; - - // ignore missing optional dependency - if (!dependency.IsRequired && requiredMod == null) - continue; + IModMetadata? requiredMod = dependency.Mod; + if (requiredMod == null) + continue; // missing dependencies are handled earlier // detect dependency loop + var subchain = new List<IModMetadata>(currentChain) { mod }; if (states[requiredMod] == ModDependencyStatus.Checking) { sortedMods.Push(mod); @@ -364,8 +362,8 @@ namespace StardewModdingAPI.Framework.ModLoading } // recursively process each dependency - var substatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); - switch (substatus) + var subStatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); + switch (subStatus) { // sorted successfully case ModDependencyStatus.Sorted: @@ -381,7 +379,7 @@ namespace StardewModdingAPI.Framework.ModLoading // unexpected status case ModDependencyStatus.Queued: case ModDependencyStatus.Checking: - throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{substatus}' status."); + throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{subStatus}' status."); // sanity check default: @@ -395,35 +393,16 @@ namespace StardewModdingAPI.Framework.ModLoading } } - /// <summary>Get all mod folders in a root folder, passing through empty folders as needed.</summary> - /// <param name="rootPath">The root folder path to search.</param> - private IEnumerable<DirectoryInfo> GetModFolders(string rootPath) - { - foreach (string modRootPath in Directory.GetDirectories(rootPath)) - { - DirectoryInfo directory = new DirectoryInfo(modRootPath); - - // if a folder only contains another folder, check the inner folder instead - while (!directory.GetFiles().Any() && directory.GetDirectories().Length == 1) - directory = directory.GetDirectories().First(); - - yield return directory; - } - } - /// <summary>Get the dependencies declared in a manifest.</summary> /// <param name="manifest">The mod manifest.</param> /// <param name="loadedMods">The loaded mods.</param> private IEnumerable<ModDependency> GetDependenciesFrom(IManifest manifest, IModMetadata[] loadedMods) { - IModMetadata FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id)); + IModMetadata? FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id)); // yield dependencies - if (manifest.Dependencies != null) - { - foreach (var entry in manifest.Dependencies) - yield return new ModDependency(entry.UniqueID, entry.MinimumVersion, FindMod(entry.UniqueID), entry.IsRequired); - } + foreach (IManifestDependency entry in manifest.Dependencies) + yield return new ModDependency(entry.UniqueID, entry.MinimumVersion, FindMod(entry.UniqueID), entry.IsRequired); // yield content pack parent if (manifest.ContentPackFor != null) @@ -432,10 +411,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Get a technical message indicating why a mod's compatibility status was overridden, if applicable.</summary> /// <param name="mod">The mod metadata.</param> - private string GetTechnicalReasonForStatusOverride(IModMetadata mod) + private string? GetTechnicalReasonForStatusOverride(IModMetadata mod) { // get compatibility list record - var data = mod.DataRecord; + ModDataRecordVersionedFields? data = mod.DataRecord; if (data == null) return null; @@ -449,14 +428,14 @@ namespace StardewModdingAPI.Framework.ModLoading }; // get reason - string[] reasons = new[] { mod.DataRecord.StatusReasonPhrase, mod.DataRecord.StatusReasonDetails } + string?[] reasons = new[] { data.StatusReasonPhrase, data.StatusReasonDetails } .Where(p => !string.IsNullOrWhiteSpace(p)) .ToArray(); // build message return $"marked {statusLabel} in SMAPI's internal compatibility list for " - + (mod.DataRecord.StatusUpperVersion != null ? $"versions up to {mod.DataRecord.StatusUpperVersion}" : "all versions") + + (data.StatusUpperVersion != null ? $"versions up to {data.StatusUpperVersion}" : "all versions") + ": " + (reasons.Any() ? string.Join(": ", reasons) : "no reason given") + "."; @@ -476,13 +455,13 @@ namespace StardewModdingAPI.Framework.ModLoading public string ID { get; } /// <summary>The minimum required version (if any).</summary> - public ISemanticVersion MinVersion { get; } + public ISemanticVersion? MinVersion { get; } /// <summary>Whether the mod shouldn't be loaded if the dependency isn't available.</summary> public bool IsRequired { get; } /// <summary>The loaded mod that fulfills the dependency (if available).</summary> - public IModMetadata Mod { get; } + public IModMetadata? Mod { get; } /********* @@ -493,7 +472,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="minVersion">The minimum required version (if any).</param> /// <param name="mod">The loaded mod that fulfills the dependency (if available).</param> /// <param name="isRequired">Whether the mod shouldn't be loaded if the dependency isn't available.</param> - public ModDependency(string id, ISemanticVersion minVersion, IModMetadata mod, bool isRequired) + public ModDependency(string id, ISemanticVersion? minVersion, IModMetadata? mod, bool isRequired) { this.ID = id; this.MinVersion = minVersion; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index be2a1c58..afe38bfd 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /********* ** Public methods *********/ - public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) + public static ConstructorInfo DeclaredConstructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return @@ -25,7 +25,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); } - public static ConstructorInfo Constructor(Type type, Type[] parameters = null) + public static ConstructorInfo Constructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index 135bd218..9c8ba2b0 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -28,7 +28,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades return new Harmony(id); } - public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")] + public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { // In Harmony 1.x you could target a virtual method that's not implemented by the // target type, but in Harmony 2.0 you need to target the concrete implementation. @@ -58,7 +59,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// <param name="prefix">The prefix method, if any.</param> /// <param name="postfix">The postfix method, if any.</param> /// <param name="transpiler">The transpiler method, if any.</param> - private string GetPatchTypesLabel(HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + private string GetPatchTypesLabel(HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { var patchTypes = new List<string>(); @@ -74,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// <summary>Get a human-readable label for the method being patched.</summary> /// <param name="method">The method being patched.</param> - private string GetMethodLabel(MethodBase method) + private string GetMethodLabel(MethodBase? method) { return method != null ? $"method {method.DeclaringType?.FullName}.{method.Name}" diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 5162dda4..2b1ca54b 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades this.ImportMethodImpl(method); } - public HarmonyMethodFacade(Type type, string name, Type[] parameters = null) + public HarmonyMethodFacade(Type type, string name, Type[]? parameters = null) { this.ImportMethodImpl(AccessTools.Method(type, name, parameters)); } @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades // internal code still handles null fine. For backwards compatibility, this bypasses // the new restriction when the mod hasn't been updated for Harmony 2.0 yet. - MethodInfo importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo? importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); if (importMethod == null) throw new InvalidOperationException("Can't find 'HarmonyMethod.ImportMethod' method"); importMethod.Invoke(this, new object[] { methodInfo }); diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs index 5f68f8d9..67569424 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs @@ -18,7 +18,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades ** Public methods *********/ /// <summary>Construct an instance.</summary> - public SpriteBatchFacade(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + public SpriteBatchFacade(GraphicsDevice graphicsDevice) + : base(graphicsDevice) { } /**** diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 857a2230..d5f4cf4a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -31,13 +31,19 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="toFieldName">The new field name to reference.</param> public FieldReplaceRewriter AddField(Type fromType, string fromFieldName, Type toType, string toFieldName) { + // validate parameters + if (fromType == null) + throw new InvalidOperationException("Can't replace a field on a null source type."); + if (toType == null) + throw new InvalidOperationException("Can't replace a field on a null target type."); + // get full type name - string fromTypeName = fromType?.FullName; + string? fromTypeName = fromType.FullName; if (fromTypeName == null) throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}."); // get target field - FieldInfo toField = toType.GetField(toFieldName); + FieldInfo? toField = toType.GetField(toFieldName); if (toField == null) throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field."); @@ -52,15 +58,15 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <inheritdoc /> public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - string declaringType = fieldRef?.DeclaringType?.FullName; + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); + string? declaringType = fieldRef?.DeclaringType?.FullName; // get mapped field - if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef.Name, out FieldInfo toField)) + if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef!.Name, out FieldInfo? toField)) return false; // replace with new field - this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} field"); + this.Phrases.Add($"{fieldRef.DeclaringType!.Name}.{fieldRef.Name} field"); instruction.Operand = module.ImportReference(toField); return this.MarkRewritten(); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs index 922d4bc4..aea490c8 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs @@ -34,7 +34,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith) { // detect Harmony - if (!(type.Scope is AssemblyNameReference scope) || scope.Name != "0Harmony") + if (type.Scope is not AssemblyNameReference scope || scope.Name != "0Harmony") return false; // rewrite Harmony 1.x type to Harmony 2.0 type @@ -57,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (this.ShouldRewrite) { // rewrite Harmony 1.x methods to Harmony 2.0 - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (this.TryRewriteMethodsToFacade(module, methodRef)) { this.OnChanged(); @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } // rewrite renamed fields - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy") @@ -93,13 +93,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <summary>Rewrite methods to use Harmony facades if needed.</summary> /// <param name="module">The assembly module containing the method reference.</param> /// <param name="methodRef">The method reference to map.</param> - private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference methodRef) + private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference? methodRef) { if (!this.ReplacedTypes) return false; // not Harmony (or already using Harmony 2.0) // get facade type - Type toType = methodRef?.DeclaringType.FullName switch + Type? toType = methodRef?.DeclaringType.FullName switch { "HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade), "HarmonyLib.AccessTools" => typeof(AccessToolsFacade), @@ -110,9 +110,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // map if there's a matching method - if (RewriteHelper.HasMatchingSignature(toType, methodRef)) + if (RewriteHelper.HasMatchingSignature(toType, methodRef!)) { - methodRef.DeclaringType = module.ImportReference(toType); + methodRef!.DeclaringType = module.ImportReference(toType); return true; } @@ -137,7 +137,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters { string fullName = type.FullName.Replace("Harmony.", "HarmonyLib."); string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName); - return Type.GetType(targetName, throwOnError: true); + return Type.GetType(targetName, throwOnError: true)!; } } } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index 57f1dd17..9c6a3980 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -31,17 +32,17 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field ref - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType)) return false; // skip if not broken - FieldDefinition fieldDefinition = fieldRef.Resolve(); - if (fieldDefinition != null && !fieldDefinition.HasConstant) + FieldDefinition? fieldDefinition = fieldRef.Resolve(); + if (fieldDefinition?.HasConstant == false) return false; // rewrite if possible - TypeDefinition declaringType = fieldRef.DeclaringType.Resolve(); + TypeDefinition? declaringType = fieldRef.DeclaringType.Resolve(); bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld; return this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead) @@ -54,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } @@ -68,8 +69,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) { // get equivalent property - PropertyDefinition property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); - MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; + PropertyDefinition? property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + MethodDefinition? method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; @@ -84,14 +85,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <summary>Try rewriting the field into a matching const field.</summary> /// <param name="instruction">The CIL instruction to rewrite.</param> /// <param name="field">The field definition.</param> - private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field) + private bool TryRewriteToConstField(Instruction instruction, FieldDefinition? field) { // must have been a static field read, and the new field must be const if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true) return false; // get opcode for value type - Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); + Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); if (loadInstruction == null) return false; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 89de437e..601ecbbc 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -31,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) return false; @@ -40,13 +41,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // get type - var type = methodRef.DeclaringType.Resolve(); + TypeDefinition? type = methodRef.DeclaringType.Resolve(); if (type == null) return false; // get method definition - MethodDefinition method = null; - foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + MethodDefinition? method = null; + foreach (MethodDefinition match in type.Methods.Where(p => p.Name == methodRef.Name)) { // reference matches initial parameters of definition if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) @@ -70,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized // rewrite method reference - foreach (Instruction loadInstruction in loadInstructions) + foreach (Instruction? loadInstruction in loadInstructions) cil.InsertBefore(instruction, loadInstruction); instruction.Operand = module.ImportReference(method); @@ -84,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 9933e2ca..2e2f6316 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -26,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromType">The type whose methods to remap.</param> /// <param name="toType">The type with methods to map to.</param> /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param> - public MethodParentRewriter(string fromType, Type toType, string nounPhrase = null) + public MethodParentRewriter(string fromType, Type toType, string? nounPhrase = null) : base(nounPhrase ?? $"{fromType.Split('.').Last()} methods") { this.FromType = fromType; @@ -37,14 +38,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromType">The type whose methods to remap.</param> /// <param name="toType">The type with methods to map to.</param> /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param> - public MethodParentRewriter(Type fromType, Type toType, string nounPhrase = null) - : this(fromType.FullName, toType, nounPhrase) { } + public MethodParentRewriter(Type fromType, Type toType, string? nounPhrase = null) + : this(fromType.FullName!, toType, nounPhrase) { } /// <inheritdoc /> public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (!this.IsMatch(methodRef)) return false; @@ -59,7 +60,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Get whether a CIL instruction matches.</summary> /// <param name="methodRef">The method reference.</param> - private bool IsMatch(MethodReference methodRef) + private bool IsMatch([NotNullWhen(true)] MethodReference? methodRef) { return methodRef != null diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index ad5cb96f..a81cb5be 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private readonly Type ToType; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromTypeFullName">The full type name to which to find references.</param> /// <param name="toType">The new type to reference.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool> shouldIgnore = null) + public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool>? shouldIgnore = null) : base($"{fromTypeFullName} type") { this.FromTypeName = fromTypeFullName; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs index 44074337..0d3aff9f 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs @@ -16,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols 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); + private readonly Dictionary<string, Stream> SymbolsByAssemblyPath = new(StringComparer.OrdinalIgnoreCase); /********* @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// <param name="fileName">The assembly file name.</param> public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { - return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, fileName); } @@ -46,7 +46,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// <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) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, symbolStream); } diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index a4ac54e2..d81d763e 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -16,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <c>TKey</c> in the above example). If all components are equal after substitution, and the /// tokens can all be mapped to the same generic type, the types are considered equal. /// </remarks> - internal class TypeReferenceComparer : IEqualityComparer<TypeReference> + internal class TypeReferenceComparer : IEqualityComparer<TypeReference?> { /********* ** Public methods @@ -24,7 +24,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Get whether the specified objects are equal.</summary> /// <param name="a">The first object to compare.</param> /// <param name="b">The second object to compare.</param> - public bool Equals(TypeReference a, TypeReference b) + public bool Equals(TypeReference? a, TypeReference? b) { if (a == null || b == null) return a == b; @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="typeB">The second type to compare.</param> private bool HeuristicallyEquals(TypeReference typeA, TypeReference typeB) { - bool HeuristicallyEquals(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap) + bool HeuristicallyEqualsImpl(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap) { // analyze type names bool hasTokensA = typeNameA.Contains("!"); @@ -80,14 +80,14 @@ namespace StardewModdingAPI.Framework.ModLoading for (int i = 0; i < symbolsA.Length; i++) { - if (!HeuristicallyEquals(symbolsA[i], symbolsB[i], tokenMap)) + if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap)) return false; } return true; } - return HeuristicallyEquals(typeA.FullName, typeB.FullName, new Dictionary<string, string>()); + return HeuristicallyEqualsImpl(typeA.FullName, typeB.FullName, new Dictionary<string, string>()); } /// <summary>Map a generic type placeholder (like <c>!0</c>) to its actual type.</summary> @@ -97,7 +97,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <returns>Returns the previously-mapped type if applicable, else the <paramref name="type"/>.</returns> private string MapPlaceholder(string placeholder, string type, IDictionary<string, string> map) { - if (map.TryGetValue(placeholder, out string result)) + if (map.TryGetValue(placeholder, out string? result)) return result; map[placeholder] = type; |