diff --git a/MemoryStreamDebugVisualizer/DebugVisualizer.slnx b/MemoryStreamDebugVisualizer/DebugVisualizer.slnx
new file mode 100644
index 0000000..2ac69b0
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/DebugVisualizer.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/MemoryStreamDebugVisualizer/Directory.Build.props b/MemoryStreamDebugVisualizer/Directory.Build.props
new file mode 100644
index 0000000..9a6f2ef
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+ Debug
+ $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))
+ $(RepoRootPath)obj\$(MSBuildProjectName)\
+ $(RepoRootPath)bin\$(MSBuildProjectName)\
+ false
+ false
+ true
+ false
+ false
+ latest
+
+
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj b/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj
new file mode 100644
index 0000000..0350e38
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj
@@ -0,0 +1,14 @@
+
+
+ netstandard2.0
+ enable
+ 12
+
+
+
+
+
+
+
+
+
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs b/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs
new file mode 100644
index 0000000..60656a9
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) PlaceholderCompany. All rights reserved.
+//
+
+namespace MemoryStreamObjectSource;
+
+using Microsoft.VisualStudio.DebuggerVisualizers;
+using System.IO;
+
+///
+/// Object source class for the MemoryStreamVisualizer.
+///
+public class MemoryStreamVisualizerObjectSource : VisualizerObjectSource
+{
+ ///
+ /// How many rows will be transfered, at most, responding to a single request.
+ ///
+ public const int RowsCountPerRequest = 1024;
+
+ ///
+ /// How many bytes will be transfered for each row.
+ ///
+ public const int RowLength = 16;
+
+ ///
+ public override void TransferData(object target, Stream incomingData, Stream outgoingData)
+ {
+ if (target is not MemoryStream memoryStream)
+ {
+ return;
+ }
+
+
+ using BinaryReader binaryReader = new(incomingData);
+ var index = binaryReader.ReadInt32(); // The extension will send the offset (Int32) to start reading from
+
+ using BinaryWriter binaryWriter = new(outgoingData);
+ var backupPosition = memoryStream.Position;
+
+ // Will reply with the current MemoryStream.Position (Int64),
+ // followed by MemoryStream.Length (Int64),
+ // followed by up to 16KB of data retrieved from the MemoryStream
+ binaryWriter.Write(backupPosition);
+ binaryWriter.Write(memoryStream.Length);
+ if (index < memoryStream.Length)
+ {
+ try
+ {
+ var data = new byte[RowsCountPerRequest * RowLength];
+ memoryStream.Seek(index, SeekOrigin.Begin);
+ var count = memoryStream.Read(data, 0, data.Length);
+ binaryWriter.Write(data, 0, count);
+ }
+ finally
+ {
+ // Make sure to restore the MemoryStream to its original position
+ memoryStream.Seek(backupPosition, SeekOrigin.Begin);
+ }
+ }
+
+ binaryWriter.Flush();
+
+ }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs
new file mode 100644
index 0000000..7119fb1
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace MemoryStreamVisualizer;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.Extensibility;
+
+///
+/// Extension entrypoint for the VisualStudio.Extensibility extension.
+///
+[VisualStudioContribution]
+internal class ExtensionEntrypoint : Extension
+{
+ ///
+ public override ExtensionConfiguration ExtensionConfiguration => new()
+ {
+ Metadata = new(
+ id: "MemoryStreamVisualizer.97a0a2fb-f163-4fa3-91f0-48a2d4ad9f57",
+ version: this.ExtensionAssemblyVersion,
+ publisherName: "Microsoft",
+ displayName: "MemoryStream Debugger Visualizer",
+ description: "A debugger visualizer for MemoryStream"),
+ };
+
+ ///
+ protected override void InitializeServices(IServiceCollection serviceCollection)
+ {
+ base.InitializeServices(serviceCollection);
+
+ // You can configure dependency injection here by adding services to the serviceCollection.
+ }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs
new file mode 100644
index 0000000..8d78106
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace MemoryStreamVisualizer;
+
+using System.Runtime.Serialization;
+
+///
+/// ViewModel class representing a row of binary data.
+///
+[DataContract]
+public class HexEditorRow
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The index of this row.
+ /// The bytes making up this row of data, in their hex representation.
+ /// The bytes making up this row of data, in their Ascii representation.
+ public HexEditorRow(int index, string data, string ascii)
+ {
+ this.Index = index;
+ this.Data = data;
+ this.Ascii = ascii;
+ }
+
+ ///
+ /// Gets the index of this row.
+ ///
+ [DataMember]
+ public int Index { get; }
+
+ ///
+ /// Gets the index of this row in hex format.
+ ///
+ [DataMember]
+ public string HexIndex => $"{this.Index:X8}h";
+
+ ///
+ /// Gets the bytes making up this row of data, in their hex representation.
+ ///
+ [DataMember]
+ public string Data { get; }
+
+ ///
+ /// Gets the bytes making up this row of data, in their Ascii representation.
+ ///
+ [DataMember]
+ public string Ascii { get; }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs
new file mode 100644
index 0000000..fa7d396
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace MemoryStreamVisualizer;
+
+using System.IO;
+using System.Runtime.Serialization;
+using System.Windows;
+using Microsoft.VisualStudio.Extensibility.UI;
+
+///
+/// ViewModel class representing the data contained by a .
+///
+[DataContract]
+public class MemoryStreamData : NotifyPropertyChangedObject
+{
+ private long length;
+ private long position;
+ private Visibility loadingVisibility = Visibility.Visible;
+
+ ///
+ /// Gets or sets the length of the .
+ ///
+ [DataMember]
+ public long Length
+ {
+ get => this.length;
+ set
+ {
+ if (this.SetProperty(ref this.length, value))
+ {
+ this.RaiseNotifyPropertyChangedEvent(nameof(this.HexLength));
+ }
+ }
+ }
+
+ ///
+ /// Gets the length of the in hex format.
+ ///
+ [DataMember]
+ public string HexLength => $"{this.length:X}h";
+
+ ///
+ /// Gets or sets the current position of the .
+ ///
+ [DataMember]
+ public long Position
+ {
+ get => this.position;
+ set
+ {
+ if (this.SetProperty(ref this.position, value))
+ {
+ this.RaiseNotifyPropertyChangedEvent(nameof(this.HexPosition));
+ }
+ }
+ }
+
+ ///
+ /// Gets the current position of the in hex format.
+ ///
+ [DataMember]
+ public string HexPosition => $"{this.position:X}h";
+
+ ///
+ /// Gets the data currently contained in the .
+ ///
+ [DataMember]
+ public ObservableList Data { get; } = new();
+
+ ///
+ /// Gets or sets whether the loading bar should be visible.
+ ///
+ [DataMember]
+ public Visibility LoadingVisibility
+ {
+ get => this.loadingVisibility;
+ set => this.SetProperty(ref this.loadingVisibility, value);
+ }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs
new file mode 100644
index 0000000..f614e63
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace MemoryStreamVisualizer;
+
+using MemoryStreamObjectSource;
+using Microsoft.VisualStudio.Extensibility;
+using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
+using Microsoft.VisualStudio.RpcContracts.RemoteUI;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+///
+/// Debugger visualizer provider for .
+///
+[VisualStudioContribution]
+internal class MemoryStreamDebuggerVisualizerProvider : DebuggerVisualizerProvider
+{
+ ///
+ public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
+ [
+ new VisualizerTargetType("%MemoryStreamVisualizer.MemoryStreamDebuggerVisualizerProvider.Name%", typeof(MemoryStream))
+ ])
+ {
+ VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(MemoryStreamVisualizerObjectSource)),
+ Style = VisualizerStyle.ToolWindow,
+ };
+
+ ///
+ public override Task CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new MemoryStreamVisualizerUserControl(visualizerTarget));
+ }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj
new file mode 100644
index 0000000..2024e28
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj
@@ -0,0 +1,29 @@
+
+
+ net8.0-windows8.0
+ enable
+ 12
+ en-US
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs
new file mode 100644
index 0000000..bdf3e66
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs
@@ -0,0 +1,150 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+
+using MemoryStreamObjectSource;
+using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
+using Microsoft.VisualStudio.Extensibility.UI;
+using Microsoft.VisualStudio.RpcContracts.DebuggerVisualizers;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace MemoryStreamVisualizer;
+///
+/// Remote UI user control for the MemoryStreamVisualizer.
+///
+internal class MemoryStreamVisualizerUserControl : RemoteUserControl
+{
+ private readonly VisualizerTarget visualizerTarget;
+ private readonly MemoryStreamData dataContext;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The visualizer target to be used to retrieve data from.
+ public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget)
+ : base(new MemoryStreamData())
+ {
+ visualizerTarget.Changed += this.VisualizerTargetStateChangedAsync;
+
+ this.dataContext = (MemoryStreamData)this.DataContext!;
+ this.visualizerTarget = visualizerTarget;
+ }
+
+ private Task VisualizerTargetStateChangedAsync(VisualizerTargetStateNotification args)
+ {
+ if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated)
+ {
+ this.dataContext.Data.Clear();
+ this.dataContext.Position = 0;
+ this.dataContext.Length = 0;
+ this.dataContext.LoadingVisibility = Visibility.Visible;
+
+ return this.RetrieveDataAsync();
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private async Task RetrieveDataAsync()
+ {
+ ReadOnlySequence data;
+ do
+ {
+ using MemoryStream memoryStream = new(sizeof(int));
+ using BinaryWriter binaryWriter = new(memoryStream);
+ int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength;
+ if (index >= 1024 * 1024)
+ {
+ break; // Let's not retrieve more than 1MB of data
+ }
+
+ binaryWriter.Write(index);
+ binaryWriter.Flush();
+ try
+ {
+ data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence(memoryStream.ToArray()), CancellationToken.None)).Value;
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ // I can get an exception if the debug session is unpaused, so I need to handle it gracefully
+ break;
+ }
+ }
+ while (data.Length > 0 && this.Read(data));
+
+ this.dataContext.LoadingVisibility = Visibility.Hidden;
+ }
+
+ private bool Read(ReadOnlySequence data)
+ {
+ int byteInRowCount = 0;
+ StringBuilder binaryText = new();
+ StringBuilder asciiText = new();
+
+ SequenceReader reader = new(data);
+ if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length))
+ {
+ return false;
+ }
+
+ this.dataContext.Position = position;
+ this.dataContext.Length = length;
+
+ if (reader.UnreadSpan.Length == 0)
+ {
+ return false; // We always receive data unless we are at the end of the MemoryStream
+ }
+
+ List rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest);
+ byte[] tmp = new byte[1];
+ while (reader.TryRead(out byte b))
+ {
+ byteInRowCount++;
+ if (byteInRowCount > 1)
+ {
+ binaryText.Append(' ');
+ }
+
+ binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture));
+ tmp[0] = b;
+ asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]);
+
+ if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength)
+ {
+ CompleteRow();
+ }
+ }
+
+ if (byteInRowCount > 0)
+ {
+ CompleteRow();
+ this.dataContext.Data.AddRange(rows);
+ return false; // We only receive partial rows at the end of the MemoryStream
+ }
+
+ this.dataContext.Data.AddRange(rows);
+ return true;
+
+ void CompleteRow()
+ {
+ rows.Add(new HexEditorRow(
+ index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength,
+ data: binaryText.ToString(),
+ ascii: asciiText.ToString()));
+
+ byteInRowCount = 0;
+ binaryText.Clear();
+ asciiText.Clear();
+ }
+ }
+}
diff --git a/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml
new file mode 100644
index 0000000..2c6b164
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hex
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MemoryStreamDebugVisualizer/TestApp/Program.cs b/MemoryStreamDebugVisualizer/TestApp/Program.cs
new file mode 100644
index 0000000..d93c1e0
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/TestApp/Program.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) PlaceholderCompany. All rights reserved.
+//
+
+using System.Text;
+
+// Initialize a MemoryStream to store user input temporarily
+using MemoryStream userInputStream = new();
+
+// Convert user input string to byte array
+string userInput = "Sample user input data";
+
+byte[] inputData = Encoding.UTF8.GetBytes(userInput);
+
+// Write user input data to the MemoryStream
+userInputStream.Write(inputData, 0, inputData.Length);
+var buffer = new byte[inputData.Length];
+userInputStream.Position = 0;
+userInputStream.Read(buffer);
+var text = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
+
+// Perform further processing with the stored user input
+Thread.Sleep(1000);
\ No newline at end of file
diff --git a/MemoryStreamDebugVisualizer/TestApp/TestApp.csproj b/MemoryStreamDebugVisualizer/TestApp/TestApp.csproj
new file mode 100644
index 0000000..489df93
--- /dev/null
+++ b/MemoryStreamDebugVisualizer/TestApp/TestApp.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+