summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs34
-rw-r--r--src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs72
-rw-r--r--src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs54
-rw-r--r--src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs40
4 files changed, 185 insertions, 15 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 86b43990..e1ad9d37 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -7,6 +7,7 @@ using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.ModLoading.Framework;
+using StardewModdingAPI.Framework.ModLoading.Symbols;
using StardewModdingAPI.Metadata;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Utilities;
@@ -34,6 +35,12 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>A minimal assembly definition resolver which resolves references to known loaded assemblies.</summary>
private readonly AssemblyDefinitionResolver AssemblyDefinitionResolver;
+ /// <summary>Provides assembly symbol readers for Mono.Cecil.</summary>
+ private readonly SymbolReaderProvider SymbolReaderProvider = new SymbolReaderProvider();
+
+ /// <summary>Provides assembly symbol writers for Mono.Cecil.</summary>
+ private readonly SymbolWriterProvider SymbolWriterProvider = new SymbolWriterProvider();
+
/// <summary>The objects to dispose as part of this instance.</summary>
private readonly HashSet<IDisposable> Disposables = new HashSet<IDisposable>();
@@ -134,20 +141,12 @@ namespace StardewModdingAPI.Framework.ModLoading
if (!oneAssembly)
this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace);
- // load PDB file if present
- byte[] symbols;
- {
- string symbolsPath = Path.Combine(Path.GetDirectoryName(assemblyPath), Path.GetFileNameWithoutExtension(assemblyPath)) + ".pdb";
- symbols = File.Exists(symbolsPath)
- ? File.ReadAllBytes(symbolsPath)
- : null;
- }
-
// load assembly
- using MemoryStream outStream = new MemoryStream();
- assembly.Definition.Write(outStream);
- byte[] bytes = outStream.ToArray();
- lastAssembly = Assembly.Load(bytes, symbols);
+ using MemoryStream outAssemblyStream = new MemoryStream();
+ using MemoryStream outSymbolStream = new MemoryStream();
+ assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider });
+ byte[] bytes = outAssemblyStream.ToArray();
+ lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray());
}
else
{
@@ -234,10 +233,15 @@ namespace StardewModdingAPI.Framework.ModLoading
if (!file.Exists)
yield break; // not a local assembly
- // read assembly
+ // read assembly and symbols
byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes));
- AssemblyDefinition assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true }));
+ {
+ FileInfo symbolsFile = new FileInfo(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb");
+ if (symbolsFile.Exists)
+ this.SymbolReaderProvider.TryAddSymbolData(file.Name, () => this.TrackForDisposal(symbolsFile.OpenRead()));
+ }
+ AssemblyDefinition assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true, ReadSymbols = true, SymbolReaderProvider = this.SymbolReaderProvider }));
// skip if already visited
if (visitedAssemblyNames.Contains(assembly.Name.Name))
diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs
new file mode 100644
index 00000000..2171895d
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs
@@ -0,0 +1,72 @@
+using System.IO;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Cecil.Pdb;
+
+namespace StardewModdingAPI.Framework.ModLoading.Symbols
+{
+ /// <summary>Reads symbol data for an assembly.</summary>
+ internal class SymbolReader : ISymbolReader
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The module for which to read symbols.</summary>
+ private readonly ModuleDefinition Module;
+
+ /// <summary>The symbol file stream.</summary>
+ private readonly Stream Stream;
+
+ /// <summary>The underlying symbol reader.</summary>
+ private ISymbolReader Reader;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="module">The module for which to read symbols.</param>
+ /// <param name="stream">The symbol file stream.</param>
+ public SymbolReader(ModuleDefinition module, Stream stream)
+ {
+ this.Module = module;
+ this.Stream = stream;
+ this.Reader = new NativePdbReaderProvider().GetSymbolReader(module, stream);
+ }
+
+ /// <summary>Get the symbol writer provider for the assembly.</summary>
+ public ISymbolWriterProvider GetWriterProvider()
+ {
+ return new PortablePdbWriterProvider();
+ }
+
+ /// <summary>Process a debug header in the symbol file.</summary>
+ /// <param name="header">The debug header.</param>
+ public bool ProcessDebugHeader(ImageDebugHeader header)
+ {
+ try
+ {
+ return this.Reader.ProcessDebugHeader(header);
+ }
+ catch
+ {
+ this.Reader.Dispose();
+ this.Reader = new PortablePdbReaderProvider().GetSymbolReader(this.Module, this.Stream);
+ return this.Reader.ProcessDebugHeader(header);
+ }
+ }
+
+ /// <summary>Read the method debug information for a method in the assembly.</summary>
+ /// <param name="method">The method definition.</param>
+ public MethodDebugInformation Read(MethodDefinition method)
+ {
+ return this.Reader.Read(method);
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ this.Reader.Dispose();
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs
new file mode 100644
index 00000000..44074337
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace StardewModdingAPI.Framework.ModLoading.Symbols
+{
+ /// <summary>Provides assembly symbol readers for Mono.Cecil.</summary>
+ internal class SymbolReaderProvider : ISymbolReaderProvider
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The underlying symbol reader provider.</summary>
+ private readonly ISymbolReaderProvider BaseProvider = new DefaultSymbolReaderProvider(throwIfNoSymbol: false);
+
+ /// <summary>The symbol data loaded by absolute assembly path.</summary>
+ private readonly Dictionary<string, Stream> SymbolsByAssemblyPath = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Add the symbol file for a given assembly name, if it's not already registered.</summary>
+ /// <param name="fileName">The assembly file name.</param>
+ /// <param name="getSymbolStream">Get the raw file stream for the symbols.</param>
+ public void TryAddSymbolData(string fileName, Func<Stream> getSymbolStream)
+ {
+ if (!this.SymbolsByAssemblyPath.ContainsKey(fileName))
+ this.SymbolsByAssemblyPath.Add(fileName, getSymbolStream());
+ }
+
+ /// <summary>Get a symbol reader for a given module and assembly name.</summary>
+ /// <param name="module">The loaded assembly module.</param>
+ /// <param name="fileName">The assembly file name.</param>
+ public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
+ {
+ return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
+ ? new SymbolReader(module, symbolData)
+ : this.BaseProvider.GetSymbolReader(module, fileName);
+ }
+
+ /// <summary>Get a symbol reader for a given module and symbol stream.</summary>
+ /// <param name="module">The loaded assembly module.</param>
+ /// <param name="symbolStream">The loaded symbol file stream.</param>
+ public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
+ {
+ return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData)
+ ? new SymbolReader(module, symbolData)
+ : this.BaseProvider.GetSymbolReader(module, symbolStream);
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs
new file mode 100644
index 00000000..8f7e05d1
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs
@@ -0,0 +1,40 @@
+using System.IO;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace StardewModdingAPI.Framework.ModLoading.Symbols
+{
+ /// <summary>Provides assembly symbol writers for Mono.Cecil.</summary>
+ internal class SymbolWriterProvider : ISymbolWriterProvider
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The default symbol writer provider.</summary>
+ private readonly ISymbolWriterProvider DefaultProvider = new DefaultSymbolWriterProvider();
+
+ /// <summary>The symbol writer provider for the portable PDB format.</summary>
+ private readonly ISymbolWriterProvider PortablePdbProvider = new PortablePdbWriterProvider();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a symbol writer for a given module and assembly path.</summary>
+ /// <param name="module">The loaded assembly module.</param>
+ /// <param name="fileName">The assembly name.</param>
+ public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName)
+ {
+ return this.DefaultProvider.GetSymbolWriter(module, fileName);
+ }
+
+ /// <summary>Get a symbol writer for a given module and symbol stream.</summary>
+ /// <param name="module">The loaded assembly module.</param>
+ /// <param name="symbolStream">The loaded symbol file stream.</param>
+ public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream)
+ {
+ // Not implemented in default native pdb writer, so fallback to portable
+ return this.PortablePdbProvider.GetSymbolWriter(module, symbolStream);
+ }
+ }
+}