From 1bef106322d890fb126e52387a0aedf2005cf6f4 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 02:20:47 +0100 Subject: [PATCH 01/10] Enhance project structure and versioning - Updated version to 0.17 in version.json - Added new SourceGenerator project with FactoryGenerator - Improved .gitignore to exclude additional build outputs - Modified Directory.Build.props for versioning and compiler settings - Registered new LogicalVolumeFactory and VirtualDiskTransport instances - Enhanced logging in various classes for better traceability - Created generate-packages.ps1 script for automated package generation --- .gitignore | 16 ++ Directory.Build.props | 4 +- DiscUtils.slnx | 3 + Library/Directory.Build.props | 3 + Library/DiscUtils.Core/DiskImageBuilder.cs | 33 +-- Library/DiscUtils.Core/FileSystemManager.cs | 46 +++- .../Internal/LogicalVolumeFactory.cs | 2 +- .../Partitions/PartitionTable.cs | 20 +- Library/DiscUtils.Core/Setup/SetupHelper.cs | 2 + Library/DiscUtils.Core/VirtualDisk.cs | 8 +- Library/DiscUtils.Core/VirtualDiskManager.cs | 156 +++++++++++-- Library/DiscUtils.Core/VolumeManager.cs | 44 +++- Library/DiscUtils/SetupHelper.cs | 7 +- .../DiscUtils.SourceGenerator.csproj | 17 ++ SourceGenerator/FactoryGenerator.cs | 205 ++++++++++++++++++ generate-packages.ps1 | 23 ++ version.json | 2 +- 17 files changed, 517 insertions(+), 74 deletions(-) create mode 100644 SourceGenerator/DiscUtils.SourceGenerator.csproj create mode 100644 SourceGenerator/FactoryGenerator.cs create mode 100644 generate-packages.ps1 diff --git a/.gitignore b/.gitignore index b207fc71c..7d87a18bc 100644 --- a/.gitignore +++ b/.gitignore @@ -386,3 +386,19 @@ artifacts-dotnet-releaser/ # Verify *.received.* *# + +# Build outputs in Library folder +Library/net*/ +Library/netstandard*/ +net10.0/ + +# Build outputs in root +netstandard*/ + +# Build outputs in Tests folder +Tests/net*/ +Tests/netstandard*/ + +# Build outputs in Utilities folder +Utilities/net*/ +Utilities/netstandard*/ diff --git a/Directory.Build.props b/Directory.Build.props index dd0d89387..90473f8b1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,11 +13,13 @@ LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.72 + 1.0.78 + 1.0.78 true CS1591;CS0649 + false diff --git a/DiscUtils.slnx b/DiscUtils.slnx index 29103d33c..461c48c61 100644 --- a/DiscUtils.slnx +++ b/DiscUtils.slnx @@ -86,4 +86,7 @@ + + + diff --git a/Library/Directory.Build.props b/Library/Directory.Build.props index 15793114e..e593a9b1f 100644 --- a/Library/Directory.Build.props +++ b/Library/Directory.Build.props @@ -26,6 +26,8 @@ $(MSBuildThisFileDirectory)../SigningKey.snk false + false + $(BaseIntermediateOutputPath)Generated false @@ -35,6 +37,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Library/DiscUtils.Core/DiskImageBuilder.cs b/Library/DiscUtils.Core/DiskImageBuilder.cs index 73663b373..b9fb00d58 100644 --- a/Library/DiscUtils.Core/DiskImageBuilder.cs +++ b/Library/DiscUtils.Core/DiskImageBuilder.cs @@ -34,8 +34,6 @@ namespace DiscUtils; /// public abstract class DiskImageBuilder { - private static Dictionary? _typeMap; - /// /// Gets or sets the geometry of this disk, as reported by the BIOS, will be implied from the content stream if not set. /// @@ -61,18 +59,7 @@ public abstract class DiskImageBuilder /// public virtual bool PreservesBiosGeometry => false; - private static Dictionary TypeMap - { - get - { - if (_typeMap == null) - { - InitializeMaps(); - } - - return _typeMap; - } - } + private static Dictionary TypeMap => VirtualDiskManager.TypeMap; /// /// Gets an instance that constructs the specified type (and variant) of virtual disk image. @@ -101,22 +88,4 @@ public static DiskImageBuilder GetBuilder(string type, string variant) /// to each logical file that comprises the disk image. For example, given a base name /// 'foo', the files 'foo.vmdk' and 'foo-flat.vmdk' could be returned. public abstract IEnumerable Build(string baseName); - - [MemberNotNull(nameof(_typeMap))] - private static void InitializeMaps() - { - var typeMap = new Dictionary(); - - foreach (var type in typeof(VirtualDisk).Assembly.GetTypes()) - { - var attr = type.GetCustomAttribute(false); - if (attr != null) - { - var factory = (VirtualDiskFactory)Activator.CreateInstance(type)!; - typeMap.Add(attr.Type, factory); - } - } - - _typeMap = typeMap; - } } \ No newline at end of file diff --git a/Library/DiscUtils.Core/FileSystemManager.cs b/Library/DiscUtils.Core/FileSystemManager.cs index 99a2e5d73..af78c4766 100644 --- a/Library/DiscUtils.Core/FileSystemManager.cs +++ b/Library/DiscUtils.Core/FileSystemManager.cs @@ -38,14 +38,19 @@ namespace DiscUtils; /// public static class FileSystemManager { - private static readonly List _factories; + private static List? _factories; - /// - /// Initializes a new instance of the FileSystemManager class. - /// - static FileSystemManager() + private static List Factories { - _factories = []; + get + { + if (_factories == null) + { + _factories = new List(); + RegisterFileSystems(typeof(FileSystemManager).Assembly); + } + return _factories; + } } /// @@ -54,6 +59,11 @@ static FileSystemManager() /// The detector for the new file systems. public static void RegisterFileSystems(VfsFileSystemFactory factory) { + if (_factories == null) + { + _factories = new List(); + } + lock (_factories) { _factories.Add(factory); @@ -70,6 +80,11 @@ public static void RegisterFileSystems(VfsFileSystemFactory factory) /// public static void RegisterFileSystems(Assembly assembly) { + if (_factories == null) + { + _factories = new List(); + } + lock (_factories) { _factories.AddRange(DetectFactories(assembly)); @@ -99,6 +114,7 @@ public static ReadOnlyCollection DetectFileSystems(Stream stream private static IEnumerable DetectFactories(Assembly assembly) { + Console.WriteLine($"FileSystemManager: Scanning assembly {assembly.FullName} for VfsFileSystemFactories"); foreach (var type in assembly.GetTypes()) { var attrib = type.GetCustomAttribute(false); @@ -107,7 +123,18 @@ private static IEnumerable DetectFactories(Assembly assemb continue; } - yield return (VfsFileSystemFactory)Activator.CreateInstance(type)!; + Console.WriteLine($"FileSystemManager: Found VfsFileSystemFactory: {type.FullName}"); + VfsFileSystemFactory? factory = null; + try + { + factory = (VfsFileSystemFactory)Activator.CreateInstance(type, true)!; + } + catch (Exception ex) + { + Console.WriteLine($"FileSystemManager: Error instantiating {type.FullName}: {ex}"); + throw; + } + yield return factory; } } @@ -116,9 +143,10 @@ private static ReadOnlyCollection DoDetect(Stream stream, Volume var detectStream = new BufferedStream(stream); var detected = new List(); - lock (_factories) + var factories = Factories; + lock (factories) { - foreach (var factory in _factories) + foreach (var factory in factories) { detected.AddRange(factory.Detect(detectStream, volume)); } diff --git a/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs b/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs index d813bb153..37f7ac924 100644 --- a/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs +++ b/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs @@ -24,7 +24,7 @@ namespace DiscUtils.Internal; -internal abstract class LogicalVolumeFactory +public abstract class LogicalVolumeFactory { public abstract bool HandlesPhysicalVolume(PhysicalVolumeInfo volume); diff --git a/Library/DiscUtils.Core/Partitions/PartitionTable.cs b/Library/DiscUtils.Core/Partitions/PartitionTable.cs index dd59c5dfb..9def19505 100644 --- a/Library/DiscUtils.Core/Partitions/PartitionTable.cs +++ b/Library/DiscUtils.Core/Partitions/PartitionTable.cs @@ -54,11 +54,13 @@ public abstract class PartitionTable /// public abstract Geometry? DiskGeometry { get; } + private static List? _factories; + private static List Factories { get { - if (field == null) + if (_factories == null) { var factories = new List(); @@ -66,17 +68,25 @@ private static List Factories { foreach (var attr in type.GetCustomAttributes(false)) { - factories.Add((PartitionTableFactory)Activator.CreateInstance(type)!); + factories.Add((PartitionTableFactory)Activator.CreateInstance(type, true)!); } } - field = factories; + _factories = factories; } - return field; + return _factories; + } + } + + internal static void RegisterPartitionTableFactory(PartitionTableFactory factory) + { + if (_factories == null) + { + _factories = new List(); } - set; + _factories.Add(factory); } /// diff --git a/Library/DiscUtils.Core/Setup/SetupHelper.cs b/Library/DiscUtils.Core/Setup/SetupHelper.cs index 8088ba71e..6543630dc 100644 --- a/Library/DiscUtils.Core/Setup/SetupHelper.cs +++ b/Library/DiscUtils.Core/Setup/SetupHelper.cs @@ -25,10 +25,12 @@ static SetupHelper() /// public static void RegisterAssembly(Assembly assembly) { + Console.WriteLine($"SetupHelper: Registering assembly {assembly.FullName}"); lock (_alreadyLoaded) { if (!_alreadyLoaded.Add(assembly.FullName ?? assembly.GetName().FullName)) { + Console.WriteLine($"SetupHelper: Assembly {assembly.FullName} already registered."); return; } diff --git a/Library/DiscUtils.Core/VirtualDisk.cs b/Library/DiscUtils.Core/VirtualDisk.cs index ee4cfe228..9ff177862 100644 --- a/Library/DiscUtils.Core/VirtualDisk.cs +++ b/Library/DiscUtils.Core/VirtualDisk.cs @@ -375,12 +375,12 @@ public static VirtualDisk CreateDisk(DiscFileSystem fileSystem, string type, str var uri = PathToUri(path); VirtualDisk result; - if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportType)) + if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportFactory)) { throw new FileNotFoundException($"Unable to parse path '{path}'", path); } - var transport = (VirtualDiskTransport)Activator.CreateInstance(transportType)!; + var transport = transportFactory(); try { @@ -499,12 +499,12 @@ public static VirtualDisk CreateDisk(DiscFileSystem fileSystem, string type, str var uri = PathToUri(path); VirtualDisk? result = null; - if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportType)) + if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportFactory)) { throw new FileNotFoundException($"Unable to parse path '{uri}'", path); } - var transport = (VirtualDiskTransport)Activator.CreateInstance(transportType)!; + var transport = transportFactory(); try { diff --git a/Library/DiscUtils.Core/VirtualDiskManager.cs b/Library/DiscUtils.Core/VirtualDiskManager.cs index b6179de34..5191decd7 100644 --- a/Library/DiscUtils.Core/VirtualDiskManager.cs +++ b/Library/DiscUtils.Core/VirtualDiskManager.cs @@ -10,15 +10,33 @@ namespace DiscUtils; /// public static class VirtualDiskManager { - static VirtualDiskManager() + private static Dictionary? _extensionMap; + private static Dictionary? _typeMap; + private static Dictionary>? _diskTransports; + + internal static Dictionary> DiskTransports { - ExtensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - TypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - DiskTransports = new Dictionary(StringComparer.OrdinalIgnoreCase); + get + { + if (_diskTransports == null) + { + InitializeMaps(); + } + return _diskTransports!; + } } - internal static Dictionary DiskTransports { get; } - internal static Dictionary ExtensionMap { get; } + internal static Dictionary ExtensionMap + { + get + { + if (_extensionMap == null) + { + InitializeMaps(); + } + return _extensionMap!; + } + } /// /// Gets the set of disk formats supported as an array of file extensions. @@ -30,32 +48,144 @@ static VirtualDiskManager() /// public static ICollection SupportedDiskTypes => TypeMap.Keys; - internal static Dictionary TypeMap { get; } + internal static Dictionary TypeMap + { + get + { + if (_typeMap == null) + { + InitializeMaps(); + } + return _typeMap!; + } + } + + private static void InitializeMaps() + { + if (_typeMap == null) + { + _extensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _typeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _diskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + // Auto-scan Core assembly if not initialized + RegisterVirtualDiskTypes(typeof(VirtualDiskManager).Assembly); + } + } + + private static void EnsureInitialized() + { + if (_typeMap == null) + { + _extensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _typeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _diskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); + } + } + + /// + /// Registers a VirtualDiskFactory instance. + /// + /// The factory to register. + public static void RegisterVirtualDiskFactory(VirtualDiskFactory factory) + { + EnsureInitialized(); + + var type = factory.GetType(); + var diskFactoryAttribute = type.GetCustomAttribute(false); + if (diskFactoryAttribute != null) + { + if (!TypeMap.ContainsKey(diskFactoryAttribute.Type)) + { + TypeMap.Add(diskFactoryAttribute.Type, factory); + } + + foreach (var extension in diskFactoryAttribute.FileExtensions) + { + if (!ExtensionMap.ContainsKey(extension)) + { + ExtensionMap.Add(extension, factory); + } + } + } + } + + /// + /// Registers a VirtualDiskTransport type. + /// + /// The URI scheme. + /// The type implementing VirtualDiskTransport. + public static void RegisterVirtualDiskTransport(string scheme, Type type) + { + EnsureInitialized(); + if (!DiskTransports.ContainsKey(scheme)) + { + DiskTransports.Add(scheme, () => (VirtualDiskTransport)Activator.CreateInstance(type, true)!); + } + } + + /// + /// Registers a VirtualDiskTransport factory. + /// + /// The URI scheme. + /// The factory method. + internal static void RegisterVirtualDiskTransport(string scheme, Func factory) + { + EnsureInitialized(); + if (!DiskTransports.ContainsKey(scheme)) + { + DiskTransports.Add(scheme, factory); + } + } /// - /// Locates VirtualDiskFactory factories attributed with VirtualDiskFactoryAttribute, and types marked with VirtualDiskTransportAttribute, that are able to work with Virtual Disk types. + /// Locates VirtualDiskFactory factories attributed with VirtualDiskFactoryAttribute, and types marked with VirtualDiskTransportAttribute, + /// that are able to work with Virtual Disk types. /// /// An assembly to scan public static void RegisterVirtualDiskTypes(Assembly assembly) { + Console.WriteLine($"VirtualDiskManager: Scanning assembly {assembly.FullName} for VirtualDiskTypes"); + EnsureInitialized(); foreach (var type in assembly.GetTypes()) { var diskFactoryAttribute = type.GetCustomAttribute(false); if (diskFactoryAttribute != null) { - var factory = (VirtualDiskFactory)Activator.CreateInstance(type)!; - TypeMap.Add(diskFactoryAttribute.Type, factory); + Console.WriteLine($"VirtualDiskManager: Found VirtualDiskFactory: {type.FullName}"); + try + { + var factory = (VirtualDiskFactory)Activator.CreateInstance(type, true)!; + TypeMap.Add(diskFactoryAttribute.Type, factory); - foreach (var extension in diskFactoryAttribute.FileExtensions) + foreach (var extension in diskFactoryAttribute.FileExtensions) + { + ExtensionMap.Add(extension, factory); + } + } + catch (Exception ex) { - ExtensionMap.Add(extension, factory); + Console.WriteLine($"VirtualDiskManager: Error instantiating {type.FullName}: {ex}"); + throw; } } var diskTransportAttribute = type.GetCustomAttribute(false); if (diskTransportAttribute != null) { - DiskTransports.Add(diskTransportAttribute.Scheme, type); + Console.WriteLine($"VirtualDiskManager: Found VirtualDiskTransport: {type.FullName} for scheme {diskTransportAttribute.Scheme}"); + DiskTransports.Add(diskTransportAttribute.Scheme, () => + { + try + { + return (VirtualDiskTransport)Activator.CreateInstance(type, true)!; + } + catch (Exception ex) + { + Console.WriteLine($"VirtualDiskManager: Error instantiating transport {type.FullName}: {ex}"); + throw; + } + }); } } } diff --git a/Library/DiscUtils.Core/VolumeManager.cs b/Library/DiscUtils.Core/VolumeManager.cs index 417f09fc3..a296cb266 100644 --- a/Library/DiscUtils.Core/VolumeManager.cs +++ b/Library/DiscUtils.Core/VolumeManager.cs @@ -82,26 +82,45 @@ public VolumeManager(Stream initialDiskContent) private static readonly object _syncObj = new(); + private static ConcurrentBag? _logicalVolumeFactories; + private static ConcurrentBag LogicalVolumeFactories { get { - if (field == null) + if (_logicalVolumeFactories == null) { lock (_syncObj) { - if (field == null) + if (_logicalVolumeFactories == null) { - var factories = new ConcurrentBag(GetLogicalVolumeFactories(_coreAssembly)); - field = factories; + _logicalVolumeFactories = new ConcurrentBag(GetLogicalVolumeFactories(_coreAssembly)); } } } - return field; + return _logicalVolumeFactories; + } + } + + /// + /// Register a new LogicalVolumeFactory instance. + /// + /// The factory to register. + public static void RegisterLogicalVolumeFactory(LogicalVolumeFactory factory) + { + if (_logicalVolumeFactories == null) + { + lock (_syncObj) + { + if (_logicalVolumeFactories == null) + { + _logicalVolumeFactories = new ConcurrentBag(); + } + } } - set; + _logicalVolumeFactories.Add(factory); } private static IEnumerable GetLogicalVolumeFactories(Assembly assembly) @@ -110,7 +129,18 @@ private static IEnumerable GetLogicalVolumeFactories(Assem { foreach (var attr in type.GetCustomAttributes(false)) { - yield return (LogicalVolumeFactory)Activator.CreateInstance(type)!; + Console.WriteLine($"VolumeManager: Found LogicalVolumeFactory {type.FullName} in {assembly.FullName}"); + LogicalVolumeFactory? factory = null; + try + { + factory = (LogicalVolumeFactory)Activator.CreateInstance(type, true)!; + } + catch (Exception ex) + { + Console.WriteLine($"VolumeManager: Error instantiating {type.FullName}: {ex}"); + throw; + } + yield return factory; } } } diff --git a/Library/DiscUtils/SetupHelper.cs b/Library/DiscUtils/SetupHelper.cs index 9e68e892d..e040eab03 100644 --- a/Library/DiscUtils/SetupHelper.cs +++ b/Library/DiscUtils/SetupHelper.cs @@ -6,6 +6,7 @@ using DiscUtils.Fat; using DiscUtils.HfsPlus; using DiscUtils.Iso9660; +using DiscUtils.Lvm; using DiscUtils.Nfs; using DiscUtils.Ntfs; using DiscUtils.OpticalDisk; @@ -22,7 +23,7 @@ namespace DiscUtils.Complete; public static class SetupHelper { - public static void SetupComplete() + public static void SetupComplete() { Setup.SetupHelper.RegisterAssembly(typeof(Store).Assembly); Setup.SetupHelper.RegisterAssembly(typeof(Disk).Assembly); @@ -53,4 +54,8 @@ public static void SetupComplete() Setup.SetupHelper.RegisterAssembly(typeof(Xva.Disk).Assembly); Setup.SetupHelper.RegisterAssembly(typeof(Lvm.LogicalVolumeManager).Assembly); } + public static void SetupCompleteAot() + { + DiscUtils.Setup.GeneratedSetupHelper.RegisterFactories(); + } } \ No newline at end of file diff --git a/SourceGenerator/DiscUtils.SourceGenerator.csproj b/SourceGenerator/DiscUtils.SourceGenerator.csproj new file mode 100644 index 000000000..11e06ed46 --- /dev/null +++ b/SourceGenerator/DiscUtils.SourceGenerator.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + true + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/SourceGenerator/FactoryGenerator.cs b/SourceGenerator/FactoryGenerator.cs new file mode 100644 index 000000000..0d76401f2 --- /dev/null +++ b/SourceGenerator/FactoryGenerator.cs @@ -0,0 +1,205 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace DiscUtils.SourceGenerator +{ + [Generator] + public class FactoryGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Debugging helper - uncomment to attach debugger + // if (!System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Launch(); + + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: IsSyntaxTargetForGeneration, + transform: GetSemanticTargetForGeneration) + .Where(m => m != null); + + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(spc, source.Left, source.Right)); + } + + private bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) + { + return node is ClassDeclarationSyntax cds && cds.BaseList != null; + } + + private INamedTypeSymbol? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol; + + if (symbol == null) return null; + + if (InheritsFrom(symbol, "DiscUtils.Vfs.VfsFileSystemFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.VirtualDiskFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.LogicalVolumeFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.VirtualDiskTransport") || + InheritsFrom(symbol, "DiscUtils.Partitions.PartitionTableFactory")) + { + return symbol; + } + + return null; + } + + private bool InheritsFrom(INamedTypeSymbol symbol, string typeName) + { + var current = symbol.BaseType; + while (current != null) + { + if (current.ToDisplayString() == typeName) + { + return true; + } + current = current.BaseType; + } + return false; + } + + private void Execute(SourceProductionContext context, Compilation compilation, System.Collections.Immutable.ImmutableArray classes) + { + try + { + if (classes.IsDefaultOrEmpty && compilation.AssemblyName != "DiscUtils" && compilation.AssemblyName != "LTRData.DiscUtils") + { + return; + } + + var distinctClasses = classes.Where(c => c != null).Distinct(SymbolEqualityComparer.Default).Cast().ToList(); + + // Generate AssemblyRegistration for libraries + if (distinctClasses.Any()) + { + GenerateAssemblyRegistration(context, compilation, distinctClasses); + } + + // If this is the main DiscUtils assembly, generate the aggregator + if (compilation.AssemblyName == "DiscUtils" || compilation.AssemblyName == "LTRData.DiscUtils") + { + GenerateSetupHelper(context, compilation); + } + } + catch (Exception ex) + { + // Generate a file with the error so we can see it + context.AddSource("GeneratorError.g.cs", SourceText.From($"/* Error: {ex.ToString()} */", Encoding.UTF8)); + } + } + + private void GenerateAssemblyRegistration(SourceProductionContext context, Compilation compilation, List classes) + { + var sb = new StringBuilder(); + var assemblyName = compilation.AssemblyName?.Replace(".", "_"); + + sb.AppendLine("using System;"); + sb.AppendLine("using DiscUtils.Core;"); + sb.AppendLine(); + sb.AppendLine($"namespace {compilation.AssemblyName}"); + sb.AppendLine("{"); + sb.AppendLine($" public static class AssemblyRegistration_{assemblyName}"); + sb.AppendLine(" {"); + sb.AppendLine(" public static void Register()"); + sb.AppendLine(" {"); + sb.AppendLine($" Console.WriteLine(\"Registering assembly: {assemblyName}\");"); + + foreach (var classSymbol in classes) + { + if (InheritsFrom(classSymbol, "DiscUtils.Vfs.VfsFileSystemFactory")) + { + sb.AppendLine($" Console.WriteLine(\"Registering FileSystemFactory: {classSymbol.ToDisplayString()}\");"); + sb.AppendLine($" DiscUtils.FileSystemManager.RegisterFileSystems(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskFactory")) + { + sb.AppendLine($" Console.WriteLine(\"Registering VirtualDiskFactory: {classSymbol.ToDisplayString()}\");"); + sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.LogicalVolumeFactory")) + { + sb.AppendLine($" Console.WriteLine(\"Registering LogicalVolumeFactory: {classSymbol.ToDisplayString()}\");"); + sb.AppendLine($" DiscUtils.VolumeManager.RegisterLogicalVolumeFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Partitions.PartitionTableFactory")) + { + sb.AppendLine($" Console.WriteLine(\"Registering PartitionTableFactory: {classSymbol.ToDisplayString()}\");"); + sb.AppendLine($" DiscUtils.Partitions.PartitionTable.RegisterPartitionTableFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskTransport")) + { + var attributes = classSymbol.GetAttributes().Where(ad => ad.AttributeClass?.ToDisplayString() == "DiscUtils.Internal.VirtualDiskTransportAttribute"); + foreach(var attr in attributes) + { + if (attr.ConstructorArguments.Length > 0) + { + string scheme = attr.ConstructorArguments[0].Value?.ToString(); + if (!string.IsNullOrEmpty(scheme)) + { + sb.AppendLine($" Console.WriteLine(\"Registering VirtualDiskTransport: {classSymbol.ToDisplayString()} for scheme {scheme}\");"); + sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskTransport(\"{scheme}\", () => new {classSymbol.ToDisplayString()}());"); + } + } + } + } + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + + context.AddSource($"AssemblyRegistration_{assemblyName}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + private void GenerateSetupHelper(SourceProductionContext context, Compilation compilation) + { + var sb = new StringBuilder(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Reflection;"); + sb.AppendLine(); + sb.AppendLine("namespace DiscUtils.Setup"); + sb.AppendLine("{"); + sb.AppendLine(" public static class GeneratedSetupHelper"); + sb.AppendLine(" {"); + sb.AppendLine(" public static void RegisterFactories()"); + sb.AppendLine(" {"); + sb.AppendLine(" Console.WriteLine(\"GeneratedSetupHelper.RegisterFactories() called.\");"); + + foreach (var reference in compilation.References) + { + if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol) + { + var assemblyName = assemblySymbol.Name; + if ((assemblyName.StartsWith("DiscUtils.") || assemblyName.StartsWith("LTRData.DiscUtils.")) && !assemblyName.EndsWith("SourceGenerator")) + { + var safeAssemblyName = assemblyName.Replace(".", "_"); + var typeName = $"{assemblyName}.AssemblyRegistration_{safeAssemblyName}"; + + var typeSymbol = compilation.GetTypeByMetadataName(typeName); + + if (typeSymbol != null) + { + sb.AppendLine($" Console.WriteLine(\"Calling {typeName}.Register()\");"); + sb.AppendLine($" {typeName}.Register();"); + } + } + } + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + + context.AddSource("GeneratedSetupHelper.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + } +} + diff --git a/generate-packages.ps1 b/generate-packages.ps1 new file mode 100644 index 000000000..30b4e2a18 --- /dev/null +++ b/generate-packages.ps1 @@ -0,0 +1,23 @@ +$outputDir = "C:\XGitPrivate\DevePXEBootStuff\DevePXEBootClean\DiscUtils\nupkgs" + +Write-Host "Cleaning output directory: $outputDir" +if (Test-Path -Path $outputDir) { + Get-ChildItem -Path $outputDir -Filter *.nupkg | Remove-Item -Force + Get-ChildItem -Path $outputDir -Filter *.snupkg | Remove-Item -Force +} else { + New-Item -ItemType Directory -Path $outputDir | Out-Null +} + +Write-Host "Cleaning solution..." +dotnet clean --configuration Release + +Write-Host "Restoring..." +dotnet restore + +Write-Host "Building..." +dotnet build --configuration Release + +Write-Host "Packing..." +dotnet pack --configuration Release --no-build --output $outputDir + +Write-Host "Done. Packages are in $outputDir" diff --git a/version.json b/version.json index 8954ca26f..b709c48bc 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.16", + "version": "0.17", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/develop$", From 1d2a8e8c8c9a5ab86921f97b87d35aa444f4d938 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 20:32:06 +0100 Subject: [PATCH 02/10] Refactor plugin registration for AoT compatibility Modernize and refactor plugin/registration system for disk image and file system types: - Bump version to 1.0.85 - Add AoT-friendly registration path for DiskImageBuilder - Simplify and streamline FileSystemManager and VirtualDiskManager initialization - Remove console logging and exception handling from factory discovery - Make LogicalVolumeFactory internal - Make VolumeManager registration internal and direct - Add SetupCompleteAot for AoT environments - General cleanup for improved startup and reliability --- Directory.Build.props | 4 +- Library/DiscUtils.Core/DiskImageBuilder.cs | 42 ++++++- Library/DiscUtils.Core/FileSystemManager.cs | 46 ++----- .../Internal/LogicalVolumeFactory.cs | 2 +- Library/DiscUtils.Core/Setup/SetupHelper.cs | 2 - Library/DiscUtils.Core/VirtualDiskManager.cs | 119 +++--------------- Library/DiscUtils.Core/VolumeManager.cs | 15 +-- Library/DiscUtils/SetupHelper.cs | 1 + 8 files changed, 70 insertions(+), 161 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 90473f8b1..f90666b32 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,8 +13,8 @@ LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.78 - 1.0.78 + 1.0.85 + 1.0.85 true diff --git a/Library/DiscUtils.Core/DiskImageBuilder.cs b/Library/DiscUtils.Core/DiskImageBuilder.cs index b9fb00d58..cf12403f9 100644 --- a/Library/DiscUtils.Core/DiskImageBuilder.cs +++ b/Library/DiscUtils.Core/DiskImageBuilder.cs @@ -34,6 +34,8 @@ namespace DiscUtils; /// public abstract class DiskImageBuilder { + private static Dictionary? _typeMap; + /// /// Gets or sets the geometry of this disk, as reported by the BIOS, will be implied from the content stream if not set. /// @@ -59,7 +61,18 @@ public abstract class DiskImageBuilder /// public virtual bool PreservesBiosGeometry => false; - private static Dictionary TypeMap => VirtualDiskManager.TypeMap; + private static Dictionary TypeMap + { + get + { + if (_typeMap == null) + { + InitializeMaps(); + } + + return _typeMap; + } + } /// /// Gets an instance that constructs the specified type (and variant) of virtual disk image. @@ -88,4 +101,31 @@ public static DiskImageBuilder GetBuilder(string type, string variant) /// to each logical file that comprises the disk image. For example, given a base name /// 'foo', the files 'foo.vmdk' and 'foo-flat.vmdk' could be returned. public abstract IEnumerable Build(string baseName); + + // Set this to true to enable AoT compatiblity + public static bool ShouldUseVirtualDiskManagerTypeMap { get; set; } + + [MemberNotNull(nameof(_typeMap))] + private static void InitializeMaps() + { + if (ShouldUseVirtualDiskManagerTypeMap) + { + _typeMap = VirtualDiskManager.TypeMap; + return; + } + + var typeMap = new Dictionary(); + + foreach (var type in typeof(VirtualDisk).Assembly.GetTypes()) + { + var attr = type.GetCustomAttribute(false); + if (attr != null) + { + var factory = (VirtualDiskFactory)Activator.CreateInstance(type)!; + typeMap.Add(attr.Type, factory); + } + } + + _typeMap = typeMap; + } } \ No newline at end of file diff --git a/Library/DiscUtils.Core/FileSystemManager.cs b/Library/DiscUtils.Core/FileSystemManager.cs index af78c4766..99a2e5d73 100644 --- a/Library/DiscUtils.Core/FileSystemManager.cs +++ b/Library/DiscUtils.Core/FileSystemManager.cs @@ -38,19 +38,14 @@ namespace DiscUtils; /// public static class FileSystemManager { - private static List? _factories; + private static readonly List _factories; - private static List Factories + /// + /// Initializes a new instance of the FileSystemManager class. + /// + static FileSystemManager() { - get - { - if (_factories == null) - { - _factories = new List(); - RegisterFileSystems(typeof(FileSystemManager).Assembly); - } - return _factories; - } + _factories = []; } /// @@ -59,11 +54,6 @@ private static List Factories /// The detector for the new file systems. public static void RegisterFileSystems(VfsFileSystemFactory factory) { - if (_factories == null) - { - _factories = new List(); - } - lock (_factories) { _factories.Add(factory); @@ -80,11 +70,6 @@ public static void RegisterFileSystems(VfsFileSystemFactory factory) /// public static void RegisterFileSystems(Assembly assembly) { - if (_factories == null) - { - _factories = new List(); - } - lock (_factories) { _factories.AddRange(DetectFactories(assembly)); @@ -114,7 +99,6 @@ public static ReadOnlyCollection DetectFileSystems(Stream stream private static IEnumerable DetectFactories(Assembly assembly) { - Console.WriteLine($"FileSystemManager: Scanning assembly {assembly.FullName} for VfsFileSystemFactories"); foreach (var type in assembly.GetTypes()) { var attrib = type.GetCustomAttribute(false); @@ -123,18 +107,7 @@ private static IEnumerable DetectFactories(Assembly assemb continue; } - Console.WriteLine($"FileSystemManager: Found VfsFileSystemFactory: {type.FullName}"); - VfsFileSystemFactory? factory = null; - try - { - factory = (VfsFileSystemFactory)Activator.CreateInstance(type, true)!; - } - catch (Exception ex) - { - Console.WriteLine($"FileSystemManager: Error instantiating {type.FullName}: {ex}"); - throw; - } - yield return factory; + yield return (VfsFileSystemFactory)Activator.CreateInstance(type)!; } } @@ -143,10 +116,9 @@ private static ReadOnlyCollection DoDetect(Stream stream, Volume var detectStream = new BufferedStream(stream); var detected = new List(); - var factories = Factories; - lock (factories) + lock (_factories) { - foreach (var factory in factories) + foreach (var factory in _factories) { detected.AddRange(factory.Detect(detectStream, volume)); } diff --git a/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs b/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs index 37f7ac924..d813bb153 100644 --- a/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs +++ b/Library/DiscUtils.Core/Internal/LogicalVolumeFactory.cs @@ -24,7 +24,7 @@ namespace DiscUtils.Internal; -public abstract class LogicalVolumeFactory +internal abstract class LogicalVolumeFactory { public abstract bool HandlesPhysicalVolume(PhysicalVolumeInfo volume); diff --git a/Library/DiscUtils.Core/Setup/SetupHelper.cs b/Library/DiscUtils.Core/Setup/SetupHelper.cs index 6543630dc..8088ba71e 100644 --- a/Library/DiscUtils.Core/Setup/SetupHelper.cs +++ b/Library/DiscUtils.Core/Setup/SetupHelper.cs @@ -25,12 +25,10 @@ static SetupHelper() /// public static void RegisterAssembly(Assembly assembly) { - Console.WriteLine($"SetupHelper: Registering assembly {assembly.FullName}"); lock (_alreadyLoaded) { if (!_alreadyLoaded.Add(assembly.FullName ?? assembly.GetName().FullName)) { - Console.WriteLine($"SetupHelper: Assembly {assembly.FullName} already registered."); return; } diff --git a/Library/DiscUtils.Core/VirtualDiskManager.cs b/Library/DiscUtils.Core/VirtualDiskManager.cs index 5191decd7..a25858960 100644 --- a/Library/DiscUtils.Core/VirtualDiskManager.cs +++ b/Library/DiscUtils.Core/VirtualDiskManager.cs @@ -1,7 +1,7 @@ -using System; +using DiscUtils.Internal; +using System; using System.Collections.Generic; using System.Reflection; -using DiscUtils.Internal; namespace DiscUtils; @@ -10,33 +10,15 @@ namespace DiscUtils; /// public static class VirtualDiskManager { - private static Dictionary? _extensionMap; - private static Dictionary? _typeMap; - private static Dictionary>? _diskTransports; - - internal static Dictionary> DiskTransports + static VirtualDiskManager() { - get - { - if (_diskTransports == null) - { - InitializeMaps(); - } - return _diskTransports!; - } + ExtensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + TypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + DiskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); } - internal static Dictionary ExtensionMap - { - get - { - if (_extensionMap == null) - { - InitializeMaps(); - } - return _extensionMap!; - } - } + internal static Dictionary> DiskTransports { get; } + internal static Dictionary ExtensionMap { get; } /// /// Gets the set of disk formats supported as an array of file extensions. @@ -48,40 +30,7 @@ internal static Dictionary ExtensionMap /// public static ICollection SupportedDiskTypes => TypeMap.Keys; - internal static Dictionary TypeMap - { - get - { - if (_typeMap == null) - { - InitializeMaps(); - } - return _typeMap!; - } - } - - private static void InitializeMaps() - { - if (_typeMap == null) - { - _extensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - _typeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - _diskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); - - // Auto-scan Core assembly if not initialized - RegisterVirtualDiskTypes(typeof(VirtualDiskManager).Assembly); - } - } - - private static void EnsureInitialized() - { - if (_typeMap == null) - { - _extensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - _typeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - _diskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } - } + internal static Dictionary TypeMap { get; } /// /// Registers a VirtualDiskFactory instance. @@ -89,8 +38,6 @@ private static void EnsureInitialized() /// The factory to register. public static void RegisterVirtualDiskFactory(VirtualDiskFactory factory) { - EnsureInitialized(); - var type = factory.GetType(); var diskFactoryAttribute = type.GetCustomAttribute(false); if (diskFactoryAttribute != null) @@ -110,20 +57,6 @@ public static void RegisterVirtualDiskFactory(VirtualDiskFactory factory) } } - /// - /// Registers a VirtualDiskTransport type. - /// - /// The URI scheme. - /// The type implementing VirtualDiskTransport. - public static void RegisterVirtualDiskTransport(string scheme, Type type) - { - EnsureInitialized(); - if (!DiskTransports.ContainsKey(scheme)) - { - DiskTransports.Add(scheme, () => (VirtualDiskTransport)Activator.CreateInstance(type, true)!); - } - } - /// /// Registers a VirtualDiskTransport factory. /// @@ -131,7 +64,6 @@ public static void RegisterVirtualDiskTransport(string scheme, Type type) /// The factory method. internal static void RegisterVirtualDiskTransport(string scheme, Func factory) { - EnsureInitialized(); if (!DiskTransports.ContainsKey(scheme)) { DiskTransports.Add(scheme, factory); @@ -145,47 +77,24 @@ internal static void RegisterVirtualDiskTransport(string scheme, FuncAn assembly to scan public static void RegisterVirtualDiskTypes(Assembly assembly) { - Console.WriteLine($"VirtualDiskManager: Scanning assembly {assembly.FullName} for VirtualDiskTypes"); - EnsureInitialized(); foreach (var type in assembly.GetTypes()) { var diskFactoryAttribute = type.GetCustomAttribute(false); if (diskFactoryAttribute != null) { - Console.WriteLine($"VirtualDiskManager: Found VirtualDiskFactory: {type.FullName}"); - try - { - var factory = (VirtualDiskFactory)Activator.CreateInstance(type, true)!; - TypeMap.Add(diskFactoryAttribute.Type, factory); + var factory = (VirtualDiskFactory)Activator.CreateInstance(type)!; + TypeMap.Add(diskFactoryAttribute.Type, factory); - foreach (var extension in diskFactoryAttribute.FileExtensions) - { - ExtensionMap.Add(extension, factory); - } - } - catch (Exception ex) + foreach (var extension in diskFactoryAttribute.FileExtensions) { - Console.WriteLine($"VirtualDiskManager: Error instantiating {type.FullName}: {ex}"); - throw; + ExtensionMap.Add(extension, factory); } } var diskTransportAttribute = type.GetCustomAttribute(false); if (diskTransportAttribute != null) { - Console.WriteLine($"VirtualDiskManager: Found VirtualDiskTransport: {type.FullName} for scheme {diskTransportAttribute.Scheme}"); - DiskTransports.Add(diskTransportAttribute.Scheme, () => - { - try - { - return (VirtualDiskTransport)Activator.CreateInstance(type, true)!; - } - catch (Exception ex) - { - Console.WriteLine($"VirtualDiskManager: Error instantiating transport {type.FullName}: {ex}"); - throw; - } - }); + DiskTransports.Add(diskTransportAttribute.Scheme, () => (VirtualDiskTransport)Activator.CreateInstance(type)!); } } } diff --git a/Library/DiscUtils.Core/VolumeManager.cs b/Library/DiscUtils.Core/VolumeManager.cs index a296cb266..90bfa2a5b 100644 --- a/Library/DiscUtils.Core/VolumeManager.cs +++ b/Library/DiscUtils.Core/VolumeManager.cs @@ -107,7 +107,7 @@ private static ConcurrentBag LogicalVolumeFactories /// Register a new LogicalVolumeFactory instance. /// /// The factory to register. - public static void RegisterLogicalVolumeFactory(LogicalVolumeFactory factory) + internal static void RegisterLogicalVolumeFactory(LogicalVolumeFactory factory) { if (_logicalVolumeFactories == null) { @@ -129,18 +129,7 @@ private static IEnumerable GetLogicalVolumeFactories(Assem { foreach (var attr in type.GetCustomAttributes(false)) { - Console.WriteLine($"VolumeManager: Found LogicalVolumeFactory {type.FullName} in {assembly.FullName}"); - LogicalVolumeFactory? factory = null; - try - { - factory = (LogicalVolumeFactory)Activator.CreateInstance(type, true)!; - } - catch (Exception ex) - { - Console.WriteLine($"VolumeManager: Error instantiating {type.FullName}: {ex}"); - throw; - } - yield return factory; + yield return (LogicalVolumeFactory)Activator.CreateInstance(type)!; } } } diff --git a/Library/DiscUtils/SetupHelper.cs b/Library/DiscUtils/SetupHelper.cs index e040eab03..4d53c4c20 100644 --- a/Library/DiscUtils/SetupHelper.cs +++ b/Library/DiscUtils/SetupHelper.cs @@ -56,6 +56,7 @@ public static void SetupComplete() } public static void SetupCompleteAot() { + DiskImageBuilder.ShouldUseVirtualDiskManagerTypeMap = true; DiscUtils.Setup.GeneratedSetupHelper.RegisterFactories(); } } \ No newline at end of file From ff230085430bef6e5aaf98c9b06afc2c7107de61 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 20:38:46 +0100 Subject: [PATCH 03/10] Refactor SetupComplete method and remove console logging from FactoryGenerator --- Library/DiscUtils/SetupHelper.cs | 3 ++- SourceGenerator/FactoryGenerator.cs | 6 ------ version.json | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Library/DiscUtils/SetupHelper.cs b/Library/DiscUtils/SetupHelper.cs index 4d53c4c20..895416adf 100644 --- a/Library/DiscUtils/SetupHelper.cs +++ b/Library/DiscUtils/SetupHelper.cs @@ -23,7 +23,7 @@ namespace DiscUtils.Complete; public static class SetupHelper { - public static void SetupComplete() + public static void SetupComplete() { Setup.SetupHelper.RegisterAssembly(typeof(Store).Assembly); Setup.SetupHelper.RegisterAssembly(typeof(Disk).Assembly); @@ -54,6 +54,7 @@ public static void SetupComplete() Setup.SetupHelper.RegisterAssembly(typeof(Xva.Disk).Assembly); Setup.SetupHelper.RegisterAssembly(typeof(Lvm.LogicalVolumeManager).Assembly); } + public static void SetupCompleteAot() { DiskImageBuilder.ShouldUseVirtualDiskManagerTypeMap = true; diff --git a/SourceGenerator/FactoryGenerator.cs b/SourceGenerator/FactoryGenerator.cs index 0d76401f2..c9b57a250 100644 --- a/SourceGenerator/FactoryGenerator.cs +++ b/SourceGenerator/FactoryGenerator.cs @@ -110,28 +110,23 @@ private void GenerateAssemblyRegistration(SourceProductionContext context, Compi sb.AppendLine(" {"); sb.AppendLine(" public static void Register()"); sb.AppendLine(" {"); - sb.AppendLine($" Console.WriteLine(\"Registering assembly: {assemblyName}\");"); foreach (var classSymbol in classes) { if (InheritsFrom(classSymbol, "DiscUtils.Vfs.VfsFileSystemFactory")) { - sb.AppendLine($" Console.WriteLine(\"Registering FileSystemFactory: {classSymbol.ToDisplayString()}\");"); sb.AppendLine($" DiscUtils.FileSystemManager.RegisterFileSystems(new {classSymbol.ToDisplayString()}());"); } else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskFactory")) { - sb.AppendLine($" Console.WriteLine(\"Registering VirtualDiskFactory: {classSymbol.ToDisplayString()}\");"); sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskFactory(new {classSymbol.ToDisplayString()}());"); } else if (InheritsFrom(classSymbol, "DiscUtils.Internal.LogicalVolumeFactory")) { - sb.AppendLine($" Console.WriteLine(\"Registering LogicalVolumeFactory: {classSymbol.ToDisplayString()}\");"); sb.AppendLine($" DiscUtils.VolumeManager.RegisterLogicalVolumeFactory(new {classSymbol.ToDisplayString()}());"); } else if (InheritsFrom(classSymbol, "DiscUtils.Partitions.PartitionTableFactory")) { - sb.AppendLine($" Console.WriteLine(\"Registering PartitionTableFactory: {classSymbol.ToDisplayString()}\");"); sb.AppendLine($" DiscUtils.Partitions.PartitionTable.RegisterPartitionTableFactory(new {classSymbol.ToDisplayString()}());"); } else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskTransport")) @@ -144,7 +139,6 @@ private void GenerateAssemblyRegistration(SourceProductionContext context, Compi string scheme = attr.ConstructorArguments[0].Value?.ToString(); if (!string.IsNullOrEmpty(scheme)) { - sb.AppendLine($" Console.WriteLine(\"Registering VirtualDiskTransport: {classSymbol.ToDisplayString()} for scheme {scheme}\");"); sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskTransport(\"{scheme}\", () => new {classSymbol.ToDisplayString()}());"); } } diff --git a/version.json b/version.json index b709c48bc..8954ca26f 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.17", + "version": "0.16", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/develop$", From 752523a558d3e4b24f23519f4eba0694ee057055 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 21:07:04 +0100 Subject: [PATCH 04/10] Refactor source generator structure and update project references --- DiscUtils.slnx | 4 +--- Library/Directory.Build.props | 5 ++++- .../DiscUtils.SourceGenerator.csproj | 9 +++++++-- .../DiscUtils.SourceGenerator}/FactoryGenerator.cs | 0 4 files changed, 12 insertions(+), 6 deletions(-) rename {SourceGenerator => Library/DiscUtils.SourceGenerator}/DiscUtils.SourceGenerator.csproj (62%) rename {SourceGenerator => Library/DiscUtils.SourceGenerator}/FactoryGenerator.cs (100%) diff --git a/DiscUtils.slnx b/DiscUtils.slnx index 461c48c61..af19f17d9 100644 --- a/DiscUtils.slnx +++ b/DiscUtils.slnx @@ -48,6 +48,7 @@ + @@ -86,7 +87,4 @@ - - - diff --git a/Library/Directory.Build.props b/Library/Directory.Build.props index e593a9b1f..ec66632c9 100644 --- a/Library/Directory.Build.props +++ b/Library/Directory.Build.props @@ -37,7 +37,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + diff --git a/SourceGenerator/DiscUtils.SourceGenerator.csproj b/Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj similarity index 62% rename from SourceGenerator/DiscUtils.SourceGenerator.csproj rename to Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj index 11e06ed46..35a9841d6 100644 --- a/SourceGenerator/DiscUtils.SourceGenerator.csproj +++ b/Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj @@ -1,9 +1,14 @@ + - net10.0 + netstandard2.0 + + true + Source Generator for DiscUtils true - true + DiscUtils;SourceGenerator;Analyzer + enable diff --git a/SourceGenerator/FactoryGenerator.cs b/Library/DiscUtils.SourceGenerator/FactoryGenerator.cs similarity index 100% rename from SourceGenerator/FactoryGenerator.cs rename to Library/DiscUtils.SourceGenerator/FactoryGenerator.cs From 61474b27522674b953e29e703a02d8446fad7b90 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 21:19:10 +0100 Subject: [PATCH 05/10] Wip --- .github/workflows/publish-nuget.yml | 21 +++++++-------------- Directory.Build.props | 3 +-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 206ae6f71..e0f29e3dc 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -1,9 +1,7 @@ name: NuGet push (tag) -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' +on: + workflow_dispatch: env: DOTNET_CLI_TELEMETRY_OPTOUT: true @@ -11,19 +9,14 @@ env: jobs: build: - runs-on: windows-2019 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 + - name: Setup .NET Core + uses: actions/setup-dotnet@v5 with: - fetch-depth: 50 - lfs: 'true' - # We do not need to fetch tags, as we're already at a tagged build - it should be available automatically - - - name: Setup .NET Core 7.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '7.0.x' + dotnet-version: 10.0.x - name: Pack run: dotnet pack -c Release -o ${{ github.workspace }}/build diff --git a/Directory.Build.props b/Directory.Build.props index f90666b32..fba138ec6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,8 +13,7 @@ LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.85 - 1.0.85 + 1.0.100 true From 07c05c685a33ba9dc511901e1b22298eb759b635 Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 21:21:38 +0100 Subject: [PATCH 06/10] Update package project URLs and identifiers to reflect correct ownership --- Directory.Build.props | 2 +- Library/Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index fba138ec6..35403b043 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ portable - https://github.com/LTRData/DiscUtils + https://github.com/Devedse/DiscUtils MIT git diff --git a/Library/Directory.Build.props b/Library/Directory.Build.props index ec66632c9..3c0fe7c12 100644 --- a/Library/Directory.Build.props +++ b/Library/Directory.Build.props @@ -5,7 +5,7 @@ netstandard2.0;netstandard2.1;net46;net48;net8.0;net9.0;net10.0 true - LTRData.$(MSBuildProjectName) + Devedse.$(MSBuildProjectName) $(LocalNuGetPath) $(FileVersion) README.md From 8d74634d843792282e82a169762aca2009c198bd Mon Sep 17 00:00:00 2001 From: Devedse Date: Thu, 18 Dec 2025 21:28:18 +0100 Subject: [PATCH 07/10] Refactor publish-nuget workflow to build before packing --- .github/workflows/publish-nuget.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index e0f29e3dc..8b4ec41c7 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -18,8 +18,11 @@ jobs: with: dotnet-version: 10.0.x + - name: Build + run: dotnet build -c Release + - name: Pack - run: dotnet pack -c Release -o ${{ github.workspace }}/build + run: dotnet pack -c Release -o ${{ github.workspace }}/build --no-build - name: NuGet push run: dotnet nuget push *.nupkg --skip-duplicate -k ${{secrets.NUGET_KEY}} -s https://api.nuget.org/v3/index.json From 3a14205793f2fc87dba2418420df632161772a8f Mon Sep 17 00:00:00 2001 From: Devedse Date: Tue, 30 Dec 2025 19:32:43 +0100 Subject: [PATCH 08/10] Fixed path separator issue in registry package --- Library/DiscUtils.Registry/RegistryKey.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Library/DiscUtils.Registry/RegistryKey.cs b/Library/DiscUtils.Registry/RegistryKey.cs index f8448d54e..553e03c9e 100644 --- a/Library/DiscUtils.Registry/RegistryKey.cs +++ b/Library/DiscUtils.Registry/RegistryKey.cs @@ -40,6 +40,8 @@ public enum RegistryValueOptions /// public sealed class RegistryKey { + internal static readonly char[] RegistryPathSeparators = ['\\']; + private readonly KeyNodeCell _cell; private readonly RegistryHive _hive; @@ -530,7 +532,7 @@ public RegistryKey CreateSubKey(string subkey) return this; } - var split = subkey.Split(Utilities.PathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); + var split = subkey.Split(RegistryPathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); var cellIndex = FindSubKeyCell(split[0]); if (cellIndex < 0) @@ -573,7 +575,7 @@ public RegistryKey OpenSubKey(string path) return this; } - var split = path.Split(Utilities.PathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); + var split = path.Split(RegistryPathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); var cellIndex = FindSubKeyCell(split[0]); if (cellIndex < 0) @@ -642,7 +644,7 @@ public bool DeleteSubKey(string subkey, bool throwOnMissingSubKey) throw new ArgumentException("Invalid SubKey", nameof(subkey)); } - var split = subkey.Split(Utilities.PathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); + var split = subkey.Split(RegistryPathSeparators, 2, StringSplitOptions.RemoveEmptyEntries); var subkeyCellIndex = FindSubKeyCell(split[0]); if (subkeyCellIndex < 0) From 433500083f358938f1b27744e0a8e69fc9264614 Mon Sep 17 00:00:00 2001 From: Devedse Date: Tue, 30 Dec 2025 19:33:36 +0100 Subject: [PATCH 09/10] Update to version 101 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 35403b043..2b5a35485 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.100 + 1.0.101 true From 277a7b49a0cdd8da18de41cb608e5c7c2cb3bc19 Mon Sep 17 00:00:00 2001 From: Devedse Date: Wed, 31 Dec 2025 01:43:50 +0100 Subject: [PATCH 10/10] Update file version to 1.0.102 and add tests for handling large data cells --- Directory.Build.props | 2 +- Library/DiscUtils.Registry/Bin.cs | 31 +++++++++++++++++++ .../LibraryTests/Registry/RegistryKeyTest.cs | 28 +++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2b5a35485..6429d7843 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.101 + 1.0.102 true diff --git a/Library/DiscUtils.Registry/Bin.cs b/Library/DiscUtils.Registry/Bin.cs index 12dcc19a6..e81d9b67a 100644 --- a/Library/DiscUtils.Registry/Bin.cs +++ b/Library/DiscUtils.Registry/Bin.cs @@ -152,6 +152,37 @@ public Span ReadRawCellData(int cellIndex, Span maxBytes) { var index = cellIndex - _header.FileOffset; var len = Math.Abs(EndianUtilities.ToInt32LittleEndian(_buffer, index)); + + // Check if this is a "big data" cell (signature "db") + // Big data cells are used for values larger than ~16KB + if (len >= 6 && _buffer[index + 4] == 0x64 && _buffer[index + 5] == 0x62) // "db" + { + // Big data format: + // 0x00: signature "db" (2 bytes) + // 0x02: number of segments (2 bytes) + // 0x04: offset to list of cell indices (4 bytes) + var numSegments = EndianUtilities.ToUInt16LittleEndian(_buffer.AsSpan(index + 6)); + var listOffset = EndianUtilities.ToInt32LittleEndian(_buffer.AsSpan(index + 8)); + + // Read the list of cell indices + var listIndex = listOffset - _header.FileOffset; + var bytesWritten = 0; + + for (var i = 0; i < numSegments && bytesWritten < maxBytes.Length; i++) + { + var segmentCellIndex = EndianUtilities.ToInt32LittleEndian(_buffer.AsSpan(listIndex + i * 4)); + var segmentIndex = segmentCellIndex - _header.FileOffset; + var segmentLen = Math.Abs(EndianUtilities.ToInt32LittleEndian(_buffer, segmentIndex)) - 4; + + var bytesToCopy = Math.Min(segmentLen, maxBytes.Length - bytesWritten); + _buffer.AsSpan(segmentIndex + 4, bytesToCopy).CopyTo(maxBytes.Slice(bytesWritten)); + bytesWritten += bytesToCopy; + } + + return maxBytes.Slice(0, bytesWritten); + } + + // Regular cell data var result = maxBytes.Slice(0, Math.Min(len - 4, maxBytes.Length)); _buffer.AsSpan(index + 4, result.Length).CopyTo(result); return result; diff --git a/Tests/LibraryTests/Registry/RegistryKeyTest.cs b/Tests/LibraryTests/Registry/RegistryKeyTest.cs index 6f099087e..81165c5bf 100644 --- a/Tests/LibraryTests/Registry/RegistryKeyTest.cs +++ b/Tests/LibraryTests/Registry/RegistryKeyTest.cs @@ -77,6 +77,34 @@ public void SetLargeValue() Assert.Equal(0xAD, readVal[5232]); } + [Fact] + public void SetVeryLargeValue_BigDataCell() + { + // Test big data cells (used for values >~16KB) + // This mimics real-world scenarios like Windows registry ProductPolicy values + var buffer = new byte[80 * 1024]; // 80KB - larger than big data threshold + + // Set some distinctive bytes at various positions + buffer[0] = 0x12; + buffer[100] = 0x34; + buffer[16384] = 0x56; // Past first 16KB boundary + buffer[32768] = 0x78; // Past second 16KB boundary + buffer[buffer.Length - 1] = 0x9A; + + hive.Root.SetValue("verybigvalue", buffer); + + var readVal = (byte[])hive.Root.GetValue("verybigvalue"); + Assert.Equal(buffer.Length, readVal.Length); + Assert.Equal(0x12, readVal[0]); + Assert.Equal(0x34, readVal[100]); + Assert.Equal(0x56, readVal[16384]); + Assert.Equal(0x78, readVal[32768]); + Assert.Equal(0x9A, readVal[buffer.Length - 1]); + + // Verify entire buffer matches + Assert.Equal(buffer, readVal); + } + [Fact] public void SetLongValue() {