diff --git a/El'GarnanPluginSystem/code (2).txt b/El'GarnanPluginSystem/code (2).txt deleted file mode 100644 index 7e47dc02..00000000 --- a/El'GarnanPluginSystem/code (2).txt +++ /dev/null @@ -1,3406 +0,0 @@ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace DotNet.Plugin.Business -{ - public class PluginLoader - { - public static List Plugins { get; set; } - - public void LoadPlugins() - { - Plugins = new List(); - - if(Directory.Exists(Constants.FolderName)) - { - string[] files = Directory.GetFiles(Constants.FolderName); - foreach (string file in files) - { - if (file.EndsWith(".dll")) - { - Assembly.LoadFile(Path.GetFullPath(file)); - } - } - } - - Type interfaceType = typeof(IPlugin); - - Type[] types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => a.GetTypes()) - .Where(p => interfaceType.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract) - .ToArray(); - - foreach(Type type in types) - { - Plugins.Add((IPlugin)Activator.CreateInstance(type)); - } - - SortPlugins(); - } - - private void SortPlugins() - { - //Idea from: https://stackoverflow.com/questions/7099741/c-sharp-list-sort-by-two-columns - Plugins = Plugins.OrderBy(a=>a.Category).ThenBy(a=>a.Name).ToList(); - } - } -} -namespace DotNet.Plugin.Business -{ - public interface IPlugin - { - string Name { get; } - string Category { get; } - string Explanation { get; } - void Go(string parameters); - } -} -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using McMaster.NETCore.Plugins.Internal; -using McMaster.NETCore.Plugins.Loader; - -namespace McMaster.NETCore.Plugins -{ - /// - /// This loader attempts to load binaries for execution (both managed assemblies and native libraries) - /// in the same way that .NET Core would if they were originally part of the .NET Core application. - /// - /// This loader reads configuration files produced by .NET Core (.deps.json and runtimeconfig.json) - /// as well as a custom file (*.config files). These files describe a list of .dlls and a set of dependencies. - /// The loader searches the plugin path, as well as any additionally specified paths, for binaries - /// which satisfy the plugin's requirements. - /// - /// - public class PluginLoader : IDisposable - { -#if FEATURE_UNLOAD - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// Enable unloading the plugin from memory. - /// - /// - /// A list of types which should be shared between the host and the plugin. - /// - /// - /// - /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md - /// - /// - /// - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, bool isUnloadable, Type[] sharedTypes) - => CreateFromAssemblyFile(assemblyFile, isUnloadable, sharedTypes, _ => { }); - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// Enable unloading the plugin from memory. - /// - /// - /// A list of types which should be shared between the host and the plugin. - /// - /// - /// - /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md - /// - /// - /// - /// A function which can be used to configure advanced options for the plugin loader. - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, bool isUnloadable, Type[] sharedTypes, Action configure) - { - return CreateFromAssemblyFile(assemblyFile, - sharedTypes, - config => - { - config.IsUnloadable = isUnloadable; - configure(config); - }); - } -#endif - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// - /// - /// A list of types which should be shared between the host and the plugin. - /// - /// - /// - /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md - /// - /// - /// - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Type[] sharedTypes) - => CreateFromAssemblyFile(assemblyFile, sharedTypes, _ => { }); - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// - /// - /// A list of types which should be shared between the host and the plugin. - /// - /// - /// - /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md - /// - /// - /// - /// A function which can be used to configure advanced options for the plugin loader. - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Type[] sharedTypes, Action configure) - { - return CreateFromAssemblyFile(assemblyFile, - config => - { - if (sharedTypes != null) - { - var uniqueAssemblies = new HashSet(); - foreach (var type in sharedTypes) - { - uniqueAssemblies.Add(type.Assembly); - } - - foreach (var assembly in uniqueAssemblies) - { - config.SharedAssemblies.Add(assembly.GetName()); - } - } - configure(config); - }); - } - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile) - => CreateFromAssemblyFile(assemblyFile, _ => { }); - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// A function which can be used to configure advanced options for the plugin loader. - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Action configure) - { - if (configure == null) - { - throw new ArgumentNullException(nameof(configure)); - } - - var config = new PluginConfig(assemblyFile); - configure(config); - return new PluginLoader(config); - } - - private readonly PluginConfig _config; - private ManagedLoadContext _context; - private readonly AssemblyLoadContextBuilder _contextBuilder; - private volatile bool _disposed; - -#if FEATURE_UNLOAD - private FileSystemWatcher? _fileWatcher; - private Debouncer? _debouncer; -#endif - - /// - /// Initialize an instance of - /// - /// The configuration for the plugin. - public PluginLoader(PluginConfig config) - { - _config = config ?? throw new ArgumentNullException(nameof(config)); - _contextBuilder = CreateLoadContextBuilder(config); - _context = (ManagedLoadContext)_contextBuilder.Build(); -#if FEATURE_UNLOAD - if (config.EnableHotReload) - { - StartFileWatcher(); - } -#endif - } - - /// - /// True when this plugin is capable of being unloaded. - /// - public bool IsUnloadable - { - get - { -#if FEATURE_UNLOAD - return _context.IsCollectible; -#else - return false; -#endif - } - } - -#if FEATURE_UNLOAD - - /// - /// This event is raised when the plugin has been reloaded. - /// If was set to true, - /// the plugin will be reloaded when files on disk are changed. - /// - public event PluginReloadedEventHandler? Reloaded; - - /// - /// The unloads and reloads the plugin assemblies. - /// This method throws if is false. - /// - public void Reload() - { - EnsureNotDisposed(); - - if (!IsUnloadable) - { - throw new InvalidOperationException("Reload cannot be used because IsUnloadable is false"); - } - - _context.Unload(); - _context = (ManagedLoadContext)_contextBuilder.Build(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Reloaded?.Invoke(this, new PluginReloadedEventArgs(this)); - } - - private void StartFileWatcher() - { - /* - This is a very simple implementation. - Some improvements that could be made in the future: - - * Watch all directories which contain assemblies that could be loaded - * Support a polling file watcher. - * Handle delete/recreate better. - - If you're interested in making improvements, feel free to send a pull request. - */ - - _debouncer = new Debouncer(_config.ReloadDelay); - - _fileWatcher = new FileSystemWatcher - { - Path = Path.GetDirectoryName(_config.MainAssemblyPath) - }; - _fileWatcher.Changed += OnFileChanged; - _fileWatcher.Filter = "*.dll"; - _fileWatcher.NotifyFilter = NotifyFilters.LastWrite; - _fileWatcher.EnableRaisingEvents = true; - } - - private void OnFileChanged(object source, FileSystemEventArgs e) - { - if (!_disposed) - { - _debouncer?.Execute(Reload); - } - } -#endif - - internal AssemblyLoadContext LoadContext => _context; - - /// - /// Load the main assembly for the plugin. - /// - public Assembly LoadDefaultAssembly() - { - EnsureNotDisposed(); - return _context.LoadAssemblyFromFilePath(_config.MainAssemblyPath); - } - - /// - /// Load an assembly by name. - /// - /// The assembly name. - /// The assembly. - public Assembly LoadAssembly(AssemblyName assemblyName) - { - EnsureNotDisposed(); - return _context.LoadFromAssemblyName(assemblyName); - } - - /// - /// Load an assembly from path. - /// - /// The assembly path. - /// The assembly. - public Assembly LoadAssemblyFromPath(string assemblyPath) - => _context.LoadAssemblyFromFilePath(assemblyPath); - - /// - /// Load an assembly by name. - /// - /// The assembly name. - /// The assembly. - public Assembly LoadAssembly(string assemblyName) - { - EnsureNotDisposed(); - return LoadAssembly(new AssemblyName(assemblyName)); - } - -#if !NETCOREAPP2_1 - /// - /// Sets the scope used by some System.Reflection APIs which might trigger assembly loading. - /// - /// See https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md for more details. - /// - /// - /// - public AssemblyLoadContext.ContextualReflectionScope EnterContextualReflection() - => _context.EnterContextualReflection(); -#endif - - /// - /// Disposes the plugin loader. This only does something if is true. - /// When true, this will unload assemblies which which were loaded during the lifetime - /// of the plugin. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _disposed = true; - -#if FEATURE_UNLOAD - if (_fileWatcher != null) - { - _fileWatcher.EnableRaisingEvents = false; - _fileWatcher.Changed -= OnFileChanged; - _fileWatcher.Dispose(); - } - - _debouncer?.Dispose(); - - if (_context.IsCollectible) - { - _context.Unload(); - } -#endif - } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(PluginLoader)); - } - } - - private static AssemblyLoadContextBuilder CreateLoadContextBuilder(PluginConfig config) - { - var builder = new AssemblyLoadContextBuilder(); - - builder.SetMainAssemblyPath(config.MainAssemblyPath); - builder.SetDefaultContext(config.DefaultContext); - - foreach (var ext in config.PrivateAssemblies) - { - builder.PreferLoadContextAssembly(ext); - } - - if (config.PreferSharedTypes) - { - builder.PreferDefaultLoadContext(true); - } - -#if FEATURE_UNLOAD - if (config.IsUnloadable || config.EnableHotReload) - { - builder.EnableUnloading(); - } - - if (config.LoadInMemory) - { - builder.PreloadAssembliesIntoMemory(); - builder.ShadowCopyNativeLibraries(); - } -#endif - - builder.IsLazyLoaded(config.IsLazyLoaded); - foreach (var assemblyName in config.SharedAssemblies) - { - builder.PreferDefaultLoadContextAssembly(assemblyName); - } - -#if !FEATURE_NATIVE_RESOLVER - - // In .NET Core 3.0, this code is unnecessary because the API, AssemblyDependencyResolver, handles parsing these files. - var baseDir = Path.GetDirectoryName(config.MainAssemblyPath); - var assemblyFileName = Path.GetFileNameWithoutExtension(config.MainAssemblyPath); - - var depsJsonFile = Path.Combine(baseDir, assemblyFileName + ".deps.json"); - if (File.Exists(depsJsonFile)) - { - builder.AddDependencyContext(depsJsonFile); - } - - var pluginRuntimeConfigFile = Path.Combine(baseDir, assemblyFileName + ".runtimeconfig.json"); - - builder.TryAddAdditionalProbingPathFromRuntimeConfig(pluginRuntimeConfigFile, includeDevConfig: true, out _); - - // Always include runtimeconfig.json from the host app. - // in some cases, like `dotnet test`, the entry assembly does not actually match with the - // runtime config file which is why we search for all files matching this extensions. - foreach (var runtimeconfig in Directory.GetFiles(AppContext.BaseDirectory, "*.runtimeconfig.json")) - { - builder.TryAddAdditionalProbingPathFromRuntimeConfig(runtimeconfig, includeDevConfig: true, out _); - } -#endif - - return builder; - } - } -} - -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; -using McMaster.NETCore.Plugins.LibraryModel; - -namespace McMaster.NETCore.Plugins.Loader -{ - /// - /// A builder for creating an instance of . - /// - public class AssemblyLoadContextBuilder - { - private readonly List _additionalProbingPaths = new(); - private readonly List _resourceProbingPaths = new(); - private readonly List _resourceProbingSubpaths = new(); - private readonly Dictionary _managedLibraries = new(StringComparer.Ordinal); - private readonly Dictionary _nativeLibraries = new(StringComparer.Ordinal); - private readonly HashSet _privateAssemblies = new(StringComparer.Ordinal); - private readonly HashSet _defaultAssemblies = new(StringComparer.Ordinal); - private AssemblyLoadContext _defaultLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; - private string? _mainAssemblyPath; - private bool _preferDefaultLoadContext; - private bool _lazyLoadReferences; - -#if FEATURE_UNLOAD - private bool _isCollectible; - private bool _loadInMemory; - private bool _shadowCopyNativeLibraries; -#endif - - /// - /// Creates an assembly load context using settings specified on the builder. - /// - /// A new ManagedLoadContext. - public AssemblyLoadContext Build() - { - var resourceProbingPaths = new List(_resourceProbingPaths); - foreach (var additionalPath in _additionalProbingPaths) - { - foreach (var subPath in _resourceProbingSubpaths) - { - resourceProbingPaths.Add(Path.Combine(additionalPath, subPath)); - } - } - - if (_mainAssemblyPath == null) - { - throw new InvalidOperationException($"Missing required property. You must call '{nameof(SetMainAssemblyPath)}' to configure the default assembly."); - } - - return new ManagedLoadContext( - _mainAssemblyPath, - _managedLibraries, - _nativeLibraries, - _privateAssemblies, - _defaultAssemblies, - _additionalProbingPaths, - resourceProbingPaths, - _defaultLoadContext, - _preferDefaultLoadContext, - _lazyLoadReferences, -#if FEATURE_UNLOAD - _isCollectible, - _loadInMemory, - _shadowCopyNativeLibraries); -#else - isCollectible: false, - loadInMemory: false, - shadowCopyNativeLibraries: false); -#endif - } - - /// - /// Set the file path to the main assembly for the context. This is used as the starting point for loading - /// other assemblies. The directory that contains it is also known as the 'app local' directory. - /// - /// The file path. Must not be null or empty. Must be an absolute path. - /// The builder. - public AssemblyLoadContextBuilder SetMainAssemblyPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("Argument must not be null or empty.", nameof(path)); - } - - if (!Path.IsPathRooted(path)) - { - throw new ArgumentException("Argument must be a full path.", nameof(path)); - } - - _mainAssemblyPath = path; - return this; - } - - /// - /// Replaces the default used by the . - /// Use this feature if the of the is not the Runtime's default load context. - /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != - /// - /// The context to set. - /// The builder. - public AssemblyLoadContextBuilder SetDefaultContext(AssemblyLoadContext context) - { - _defaultLoadContext = context ?? throw new ArgumentException($"Bad Argument: AssemblyLoadContext in {nameof(AssemblyLoadContextBuilder)}.{nameof(SetDefaultContext)} is null."); - return this; - } - - /// - /// Instructs the load context to prefer a private version of this assembly, even if that version is - /// different from the version used by the host application. - /// Use this when you do not need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// This may mean the types loaded from - /// this assembly will not match the types from an assembly with the same name, but different version, - /// in the host application. - /// - /// - /// For example, if the host application has a type named Foo from assembly Banana, Version=1.0.0.0 - /// and the load context prefers a private version of Banan, Version=2.0.0.0, when comparing two objects, - /// one created by the host (Foo1) and one created from within the load context (Foo2), they will not have the same - /// type. Foo1.GetType() != Foo2.GetType() - /// - /// - /// The name of the assembly. - /// The builder. - public AssemblyLoadContextBuilder PreferLoadContextAssembly(AssemblyName assemblyName) - { - if (assemblyName.Name != null) - { - _privateAssemblies.Add(assemblyName.Name); - } - - return this; - } - - /// - /// Instructs the load context to first attempt to load assemblies by this name from the default app context, even - /// if other assemblies in this load context express a dependency on a higher or lower version. - /// Use this when you need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// The name of the assembly. - /// The builder. - public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName) - { - // Lazy loaded references have dependencies resolved as they are loaded inside the actual Load Context. - if (_lazyLoadReferences) - { - if (assemblyName.Name != null && !_defaultAssemblies.Contains(assemblyName.Name)) - { - _defaultAssemblies.Add(assemblyName.Name); - var assembly = _defaultLoadContext.LoadFromAssemblyName(assemblyName); - foreach (var reference in assembly.GetReferencedAssemblies()) - { - if (reference.Name != null) - { - _defaultAssemblies.Add(reference.Name); - } - } - } - - return this; - } - - var names = new Queue(); - names.Enqueue(assemblyName); - while (names.TryDequeue(out var name)) - { - if (name.Name == null || _defaultAssemblies.Contains(name.Name)) - { - // base cases - continue; - } - - _defaultAssemblies.Add(name.Name); - - // Load and find all dependencies of default assemblies. - // This sacrifices some performance for determinism in how transitive - // dependencies will be shared between host and plugin. - var assembly = _defaultLoadContext.LoadFromAssemblyName(name); - - foreach (var reference in assembly.GetReferencedAssemblies()) - { - names.Enqueue(reference); - } - } - - return this; - } - - /// - /// Instructs the load context to first search for binaries from the default app context, even - /// if other assemblies in this load context express a dependency on a higher or lower version. - /// Use this when you need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// This may mean the types loaded from within the context are force-downgraded to the version provided - /// by the host. can be used to selectively identify binaries - /// which should not be loaded from the default load context. - /// - /// - /// When true, first attemp to load binaries from the default load context. - /// The builder. - public AssemblyLoadContextBuilder PreferDefaultLoadContext(bool preferDefaultLoadContext) - { - _preferDefaultLoadContext = preferDefaultLoadContext; - return this; - } - - /// - /// Instructs the load context to lazy load dependencies of all shared assemblies. - /// Reduces plugin load time at the expense of non-determinism in how transitive dependencies are loaded - /// between the plugin and the host. - /// - /// Please be aware of the danger of using this option: - /// - /// https://github.com/natemcmaster/DotNetCorePlugins/pull/164#issuecomment-751557873 - /// - /// - /// True to lazy load, else false. - /// The builder. - public AssemblyLoadContextBuilder IsLazyLoaded(bool isLazyLoaded) - { - _lazyLoadReferences = isLazyLoaded; - return this; - } - - /// - /// Add a managed library to the load context. - /// - /// The managed library. - /// The builder. - public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library) - { - ValidateRelativePath(library.AdditionalProbingPath); - - if (library.Name.Name != null) - { - _managedLibraries.Add(library.Name.Name, library); - } - - return this; - } - - /// - /// Add a native library to the load context. - /// - /// - /// - public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library) - { - ValidateRelativePath(library.AppLocalPath); - ValidateRelativePath(library.AdditionalProbingPath); - - _nativeLibraries.Add(library.Name, library); - return this; - } - - /// - /// Add a that should be used to search for native and managed libraries. - /// - /// The file path. Must be a full file path. - /// The builder - public AssemblyLoadContextBuilder AddProbingPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("Value must not be null or empty.", nameof(path)); - } - - if (!Path.IsPathRooted(path)) - { - throw new ArgumentException("Argument must be a full path.", nameof(path)); - } - - _additionalProbingPaths.Add(path); - return this; - } - - /// - /// Add a that should be use to search for resource assemblies (aka satellite assemblies). - /// - /// The file path. Must be a full file path. - /// The builder - public AssemblyLoadContextBuilder AddResourceProbingPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("Value must not be null or empty.", nameof(path)); - } - - if (!Path.IsPathRooted(path)) - { - throw new ArgumentException("Argument must be a full path.", nameof(path)); - } - - _resourceProbingPaths.Add(path); - return this; - } - -#if FEATURE_UNLOAD - /// - /// Enable unloading the assembly load context. - /// - /// The builder - public AssemblyLoadContextBuilder EnableUnloading() - { - _isCollectible = true; - return this; - } - - /// - /// Read .dll files into memory to avoid locking the files. - /// This is not as efficient, so is not enabled by default, but is required for scenarios - /// like hot reloading. - /// - /// The builder - public AssemblyLoadContextBuilder PreloadAssembliesIntoMemory() - { - _loadInMemory = true; // required to prevent dotnet from locking loaded files - return this; - } - - /// - /// Shadow copy native libraries (unmanaged DLLs) to avoid locking of these files. - /// This is not as efficient, so is not enabled by default, but is required for scenarios - /// like hot reloading of plugins dependent on native libraries. - /// - /// The builder - public AssemblyLoadContextBuilder ShadowCopyNativeLibraries() - { - _shadowCopyNativeLibraries = true; - return this; - } -#endif - - /// - /// Add a that should be use to search for resource assemblies (aka satellite assemblies) - /// relative to any paths specified as - /// - /// The file path. Must not be a full file path since it will be appended to additional probing path roots. - /// The builder - internal AssemblyLoadContextBuilder AddResourceProbingSubpath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("Value must not be null or empty.", nameof(path)); - } - - if (Path.IsPathRooted(path)) - { - throw new ArgumentException("Argument must be not a full path.", nameof(path)); - } - - _resourceProbingSubpaths.Add(path); - return this; - } - - private static void ValidateRelativePath(string probingPath) - { - if (string.IsNullOrEmpty(probingPath)) - { - throw new ArgumentException("Value must not be null or empty.", nameof(probingPath)); - } - - if (Path.IsPathRooted(probingPath)) - { - throw new ArgumentException("Argument must be a relative path.", nameof(probingPath)); - } - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using McMaster.NETCore.Plugins.LibraryModel; -using Microsoft.Extensions.DependencyModel; -using NativeLibrary = McMaster.NETCore.Plugins.LibraryModel.NativeLibrary; - -namespace McMaster.NETCore.Plugins.Loader -{ - /// - /// Extensions for configuring a load context using .deps.json files. - /// - public static class DependencyContextExtensions - { - /// - /// Add dependency information to a load context from a .deps.json file. - /// - /// The builder. - /// The full path to the .deps.json file. - /// An error, if one occurs while reading .deps.json - /// The builder. - public static AssemblyLoadContextBuilder TryAddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath, out Exception? error) - { - error = null; - try - { - builder.AddDependencyContext(depsFilePath); - } - catch (Exception ex) - { - error = ex; - } - - return builder; - } - - /// - /// Add dependency information to a load context from a .deps.json file. - /// - /// The builder. - /// The full path to the .deps.json file. - /// The builder. - public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath) - { - - var reader = new DependencyContextJsonReader(); - using (var file = File.OpenRead(depsFilePath)) - { - var deps = reader.Read(file); - builder.AddDependencyContext(deps); - } - - return builder; - } - - private static string GetFallbackRid() - { - // see https://github.com/dotnet/core-setup/blob/b64f7fffbd14a3517186b9a9d5cc001ab6e5bde6/src/corehost/common/pal.h#L53-L73 - - string ridBase; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - ridBase = "win10"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - ridBase = "linux"; - - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - ridBase = "osx.10.12"; - } - else - { - return "any"; - } - - return RuntimeInformation.OSArchitecture switch - { - Architecture.X86 => ridBase + "-x86", - Architecture.X64 => ridBase + "-x64", - Architecture.Arm => ridBase + "-arm", - Architecture.Arm64 => ridBase + "-arm64", - _ => ridBase, - }; - } - - /// - /// Add a pre-parsed to the load context. - /// - /// The builder. - /// The dependency context. - /// The builder. - public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, DependencyContext dependencyContext) - { - var ridGraph = dependencyContext.RuntimeGraph.Any() || DependencyContext.Default == null - ? dependencyContext.RuntimeGraph - : DependencyContext.Default.RuntimeGraph; - - var rid = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier(); - var fallbackRid = GetFallbackRid(); - var fallbackGraph = ridGraph.FirstOrDefault(g => g.Runtime == rid) - ?? ridGraph.FirstOrDefault(g => g.Runtime == fallbackRid) - ?? new RuntimeFallbacks("any"); - - foreach (var managed in dependencyContext.ResolveRuntimeAssemblies(fallbackGraph)) - { - builder.AddManagedLibrary(managed); - } - - foreach (var library in dependencyContext.ResolveResourceAssemblies()) - { - foreach (var resource in library.ResourceAssemblies) - { - /* - * For resource assemblies, look in $packageRoot/$packageId/$version/$resourceGrandparent - * - * For example, a deps file may contain - * - * "Example/1.0.0": { - * "runtime": { - * "lib/netcoreapp2.0/Example.dll": { } - * }, - * "resources": { - * "lib/netcoreapp2.0/es/Example.resources.dll": { - * "locale": "es" - * } - * } - * } - * - * In this case, probing should happen in $packageRoot/example/1.0.0/lib/netcoreapp2.0 - */ - - var resourceDir = Path.GetDirectoryName(Path.GetDirectoryName(resource.Path)); - - if (resourceDir != null) - { - var path = Path.Combine(library.Name.ToLowerInvariant(), - library.Version, - resourceDir); - - builder.AddResourceProbingSubpath(path); - } - } - } - - foreach (var native in dependencyContext.ResolveNativeAssets(fallbackGraph)) - { - builder.AddNativeLibrary(native); - } - - return builder; - } - - private static IEnumerable ResolveRuntimeAssemblies(this DependencyContext depContext, RuntimeFallbacks runtimeGraph) - { - var rids = GetRids(runtimeGraph); - return from library in depContext.RuntimeLibraries - from assetPath in SelectAssets(rids, library.RuntimeAssemblyGroups) - select ManagedLibrary.CreateFromPackage(library.Name, library.Version, assetPath); - } - - private static IEnumerable ResolveResourceAssemblies(this DependencyContext depContext) - { - return from library in depContext.RuntimeLibraries - where library.ResourceAssemblies != null && library.ResourceAssemblies.Count > 0 - select library; - } - - private static IEnumerable ResolveNativeAssets(this DependencyContext depContext, RuntimeFallbacks runtimeGraph) - { - var rids = GetRids(runtimeGraph); - return from library in depContext.RuntimeLibraries - from assetPath in SelectAssets(rids, library.NativeLibraryGroups) - // some packages include symbols alongside native assets, such as System.Native.a or pwshplugin.pdb - where PlatformInformation.NativeLibraryExtensions.Contains(Path.GetExtension(assetPath), StringComparer.OrdinalIgnoreCase) - select NativeLibrary.CreateFromPackage(library.Name, library.Version, assetPath); - } - - private static IEnumerable GetRids(RuntimeFallbacks runtimeGraph) - { - return new[] { runtimeGraph.Runtime }.Concat(runtimeGraph?.Fallbacks ?? Enumerable.Empty()); - } - - private static IEnumerable SelectAssets(IEnumerable rids, IEnumerable groups) - { - foreach (var rid in rids) - { - var group = groups.FirstOrDefault(g => g.Runtime == rid); - if (group != null) - { - return group.AssetPaths; - } - } - - // Return the RID-agnostic group - return groups.GetDefaultAssets(); - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; -using McMaster.NETCore.Plugins.LibraryModel; - -namespace McMaster.NETCore.Plugins.Loader -{ - /// - /// An implementation of which attempts to load managed and native - /// binaries at runtime immitating some of the behaviors of corehost. - /// - [DebuggerDisplay("'{Name}' ({_mainAssemblyPath})")] - internal class ManagedLoadContext : AssemblyLoadContext - { - private readonly string _basePath; - private readonly string _mainAssemblyPath; - private readonly IReadOnlyDictionary _managedAssemblies; - private readonly IReadOnlyDictionary _nativeLibraries; - private readonly IReadOnlyCollection _privateAssemblies; - private readonly ICollection _defaultAssemblies; - private readonly IReadOnlyCollection _additionalProbingPaths; - private readonly bool _preferDefaultLoadContext; - private readonly string[] _resourceRoots; - private readonly bool _loadInMemory; - private readonly bool _lazyLoadReferences; - private readonly AssemblyLoadContext _defaultLoadContext; -#if FEATURE_NATIVE_RESOLVER - private readonly AssemblyDependencyResolver _dependencyResolver; -#endif - private readonly bool _shadowCopyNativeLibraries; - private readonly string _unmanagedDllShadowCopyDirectoryPath; - - public ManagedLoadContext(string mainAssemblyPath, - IReadOnlyDictionary managedAssemblies, - IReadOnlyDictionary nativeLibraries, - IReadOnlyCollection privateAssemblies, - IReadOnlyCollection defaultAssemblies, - IReadOnlyCollection additionalProbingPaths, - IReadOnlyCollection resourceProbingPaths, - AssemblyLoadContext defaultLoadContext, - bool preferDefaultLoadContext, - bool lazyLoadReferences, - bool isCollectible, - bool loadInMemory, - bool shadowCopyNativeLibraries) -#if FEATURE_UNLOAD - : base(Path.GetFileNameWithoutExtension(mainAssemblyPath), isCollectible) -#endif - { - if (resourceProbingPaths == null) - { - throw new ArgumentNullException(nameof(resourceProbingPaths)); - } - - _mainAssemblyPath = mainAssemblyPath ?? throw new ArgumentNullException(nameof(mainAssemblyPath)); -#if FEATURE_NATIVE_RESOLVER - _dependencyResolver = new AssemblyDependencyResolver(mainAssemblyPath); -#endif - _basePath = Path.GetDirectoryName(mainAssemblyPath) ?? throw new ArgumentException(nameof(mainAssemblyPath)); - _managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies)); - _privateAssemblies = privateAssemblies ?? throw new ArgumentNullException(nameof(privateAssemblies)); - _defaultAssemblies = defaultAssemblies != null ? defaultAssemblies.ToList() : throw new ArgumentNullException(nameof(defaultAssemblies)); - _nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries)); - _additionalProbingPaths = additionalProbingPaths ?? throw new ArgumentNullException(nameof(additionalProbingPaths)); - _defaultLoadContext = defaultLoadContext; - _preferDefaultLoadContext = preferDefaultLoadContext; - _loadInMemory = loadInMemory; - _lazyLoadReferences = lazyLoadReferences; - - _resourceRoots = new[] { _basePath } - .Concat(resourceProbingPaths) - .ToArray(); - - _shadowCopyNativeLibraries = shadowCopyNativeLibraries; - _unmanagedDllShadowCopyDirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - if (shadowCopyNativeLibraries) - { - Unloading += _ => OnUnloaded(); - } - } - - /// - /// Load an assembly. - /// - /// - /// - protected override Assembly? Load(AssemblyName assemblyName) - { - if (assemblyName.Name == null) - { - // not sure how to handle this case. It's technically possible. - return null; - } - - if ((_preferDefaultLoadContext || _defaultAssemblies.Contains(assemblyName.Name)) && !_privateAssemblies.Contains(assemblyName.Name)) - { - // If default context is preferred, check first for types in the default context unless the dependency has been declared as private - try - { - var defaultAssembly = _defaultLoadContext.LoadFromAssemblyName(assemblyName); - if (defaultAssembly != null) - { - // Add referenced assemblies to the list of default assemblies. - // This is basically lazy loading - if (_lazyLoadReferences) - { - foreach (var reference in defaultAssembly.GetReferencedAssemblies()) - { - if (reference.Name != null && !_defaultAssemblies.Contains(reference.Name)) - { - _defaultAssemblies.Add(reference.Name); - } - } - } - - // Older versions used to return null here such that returned assembly would be resolved from the default ALC. - // However, with the addition of custom default ALCs, the Default ALC may not be the user's chosen ALC when - // this context was built. As such, we simply return the Assembly from the user's chosen default load context. - return defaultAssembly; - } - } - catch - { - // Swallow errors in loading from the default context - } - } - -#if FEATURE_NATIVE_RESOLVER - var resolvedPath = _dependencyResolver.ResolveAssemblyToPath(assemblyName); - if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) - { - return LoadAssemblyFromFilePath(resolvedPath); - } -#endif - - // Resource assembly binding does not use the TPA. Instead, it probes PLATFORM_RESOURCE_ROOTS (a list of folders) - // for $folder/$culture/$assemblyName.dll - // See https://github.com/dotnet/coreclr/blob/3fca50a36e62a7433d7601d805d38de6baee7951/src/binder/assemblybinder.cpp#L1232-L1290 - - if (!string.IsNullOrEmpty(assemblyName.CultureName) && !string.Equals("neutral", assemblyName.CultureName)) - { - foreach (var resourceRoot in _resourceRoots) - { - var resourcePath = Path.Combine(resourceRoot, assemblyName.CultureName, assemblyName.Name + ".dll"); - if (File.Exists(resourcePath)) - { - return LoadAssemblyFromFilePath(resourcePath); - } - } - - return null; - } - - if (_managedAssemblies.TryGetValue(assemblyName.Name, out var library) && library != null) - { - if (SearchForLibrary(library, out var path) && path != null) - { - return LoadAssemblyFromFilePath(path); - } - } - else - { - // if an assembly was not listed in the list of known assemblies, - // fallback to the load context base directory - var dllName = assemblyName.Name + ".dll"; - foreach (var probingPath in _additionalProbingPaths.Prepend(_basePath)) - { - var localFile = Path.Combine(probingPath, dllName); - if (File.Exists(localFile)) - { - return LoadAssemblyFromFilePath(localFile); - } - } - } - - return null; - } - - public Assembly LoadAssemblyFromFilePath(string path) - { - if (!_loadInMemory) - { - return LoadFromAssemblyPath(path); - } - - using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); - var pdbPath = Path.ChangeExtension(path, ".pdb"); - if (File.Exists(pdbPath)) - { - using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); - return LoadFromStream(file, pdbFile); - } - return LoadFromStream(file); - - } - - /// - /// Loads the unmanaged binary using configured list of native libraries. - /// - /// - /// - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { -#if FEATURE_NATIVE_RESOLVER - var resolvedPath = _dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) - { - return LoadUnmanagedDllFromResolvedPath(resolvedPath, normalizePath: false); - } -#endif - - foreach (var prefix in PlatformInformation.NativeLibraryPrefixes) - { - if (_nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library)) - { - if (SearchForLibrary(library, prefix, out var path) && path != null) - { - return LoadUnmanagedDllFromResolvedPath(path); - } - } - else - { - // coreclr allows code to use [DllImport("sni")] or [DllImport("sni.dll")] - // This library treats the file name without the extension as the lookup name, - // so this loop is necessary to check if the unmanaged name matches a library - // when the file extension has been trimmed. - foreach (var suffix in PlatformInformation.NativeLibraryExtensions) - { - if (!unmanagedDllName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // check to see if there is a library entry for the library without the file extension - var trimmedName = unmanagedDllName.Substring(0, unmanagedDllName.Length - suffix.Length); - - if (_nativeLibraries.TryGetValue(prefix + trimmedName, out library)) - { - if (SearchForLibrary(library, prefix, out var path) && path != null) - { - return LoadUnmanagedDllFromResolvedPath(path); - } - } - else - { - // fallback to native assets which match the file name in the plugin base directory - var prefixSuffixDllName = prefix + unmanagedDllName + suffix; - var prefixDllName = prefix + unmanagedDllName; - - foreach (var probingPath in _additionalProbingPaths.Prepend(_basePath)) - { - var localFile = Path.Combine(probingPath, prefixSuffixDllName); - if (File.Exists(localFile)) - { - return LoadUnmanagedDllFromResolvedPath(localFile); - } - - var localFileWithoutSuffix = Path.Combine(probingPath, prefixDllName); - if (File.Exists(localFileWithoutSuffix)) - { - return LoadUnmanagedDllFromResolvedPath(localFileWithoutSuffix); - } - } - - } - } - - } - } - - return base.LoadUnmanagedDll(unmanagedDllName); - } - - private bool SearchForLibrary(ManagedLibrary library, out string? path) - { - // 1. Check for in _basePath + app local path - var localFile = Path.Combine(_basePath, library.AppLocalPath); - if (File.Exists(localFile)) - { - path = localFile; - return true; - } - - // 2. Search additional probing paths - foreach (var searchPath in _additionalProbingPaths) - { - var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - // 3. Search in base path - foreach (var ext in PlatformInformation.ManagedAssemblyExtensions) - { - var local = Path.Combine(_basePath, library.Name.Name + ext); - if (File.Exists(local)) - { - path = local; - return true; - } - } - - path = null; - return false; - } - - private bool SearchForLibrary(NativeLibrary library, string prefix, out string? path) - { - // 1. Search in base path - foreach (var ext in PlatformInformation.NativeLibraryExtensions) - { - var candidate = Path.Combine(_basePath, $"{prefix}{library.Name}{ext}"); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - // 2. Search in base path + app local (for portable deployments of netcoreapp) - var local = Path.Combine(_basePath, library.AppLocalPath); - if (File.Exists(local)) - { - path = local; - return true; - } - - // 3. Search additional probing paths - foreach (var searchPath in _additionalProbingPaths) - { - var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - path = null; - return false; - } - - private IntPtr LoadUnmanagedDllFromResolvedPath(string unmanagedDllPath, bool normalizePath = true) - { - if (normalizePath) - { - unmanagedDllPath = Path.GetFullPath(unmanagedDllPath); - } - - return _shadowCopyNativeLibraries - ? LoadUnmanagedDllFromShadowCopy(unmanagedDllPath) - : LoadUnmanagedDllFromPath(unmanagedDllPath); - } - - private IntPtr LoadUnmanagedDllFromShadowCopy(string unmanagedDllPath) - { - var shadowCopyDllPath = CreateShadowCopy(unmanagedDllPath); - - return LoadUnmanagedDllFromPath(shadowCopyDllPath); - } - - private string CreateShadowCopy(string dllPath) - { - Directory.CreateDirectory(_unmanagedDllShadowCopyDirectoryPath); - - var dllFileName = Path.GetFileName(dllPath); - var shadowCopyPath = Path.Combine(_unmanagedDllShadowCopyDirectoryPath, dllFileName); - - if (!File.Exists(shadowCopyPath)) - { - File.Copy(dllPath, shadowCopyPath); - } - - return shadowCopyPath; - } - - private void OnUnloaded() - { - if (!_shadowCopyNativeLibraries || !Directory.Exists(_unmanagedDllShadowCopyDirectoryPath)) - { - return; - } - - // Attempt to delete shadow copies - try - { - Directory.Delete(_unmanagedDllShadowCopyDirectoryPath, recursive: true); - } - catch (Exception) - { - // Files might be locked by host process. Nothing we can do about it, I guess. - } - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Text.Json; - -namespace McMaster.NETCore.Plugins.Loader -{ - /// - /// Extensions for creating a load context using settings from a runtimeconfig.json file - /// - public static class RuntimeConfigExtensions - { - private const string JsonExt = ".json"; - private static readonly JsonSerializerOptions s_serializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - /// - /// Adds additional probing paths to a managed load context using settings found in the runtimeconfig.json - /// and runtimeconfig.dev.json files. - /// - /// The context builder - /// The path to the runtimeconfig.json file - /// Also read runtimeconfig.dev.json file, if present. - /// The error, if one occurs while parsing runtimeconfig.json - /// The builder. - public static AssemblyLoadContextBuilder TryAddAdditionalProbingPathFromRuntimeConfig( - this AssemblyLoadContextBuilder builder, - string runtimeConfigPath, - bool includeDevConfig, - out Exception? error) - { - error = null; - try - { - var config = TryReadConfig(runtimeConfigPath); - if (config == null) - { - return builder; - } - - RuntimeConfig? devConfig = null; - if (includeDevConfig) - { - var configDevPath = runtimeConfigPath.Substring(0, runtimeConfigPath.Length - JsonExt.Length) + ".dev.json"; - devConfig = TryReadConfig(configDevPath); - } - - var tfm = config.runtimeOptions?.Tfm ?? devConfig?.runtimeOptions?.Tfm; - - if (config.runtimeOptions != null) - { - AddProbingPaths(builder, config.runtimeOptions, tfm); - } - - if (devConfig?.runtimeOptions != null) - { - AddProbingPaths(builder, devConfig.runtimeOptions, tfm); - } - - if (tfm != null) - { - var dotnet = Process.GetCurrentProcess().MainModule.FileName; - if (string.Equals(Path.GetFileNameWithoutExtension(dotnet), "dotnet", StringComparison.OrdinalIgnoreCase)) - { - var dotnetHome = Path.GetDirectoryName(dotnet); - if (dotnetHome != null) - { - builder.AddProbingPath(Path.Combine(dotnetHome, "store", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(), tfm)); - } - } - } - } - catch (Exception ex) - { - error = ex; - } - return builder; - } - - private static void AddProbingPaths(AssemblyLoadContextBuilder builder, RuntimeOptions options, string? tfm) - { - if (options.AdditionalProbingPaths == null) - { - return; - } - - foreach (var item in options.AdditionalProbingPaths) - { - var path = item; - if (path.Contains("|arch|")) - { - path = path.Replace("|arch|", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant()); - } - - if (path.Contains("|tfm|")) - { - if (tfm == null) - { - // We don't have enough information to parse this - continue; - } - - path = path.Replace("|tfm|", tfm); - } - - builder.AddProbingPath(path); - } - } - - private static RuntimeConfig? TryReadConfig(string path) - { - try - { - var file = File.ReadAllBytes(path); - return JsonSerializer.Deserialize(file, s_serializerOptions); - } - catch - { - return null; - } - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; - -namespace McMaster.NETCore.Plugins.LibraryModel -{ - /// - /// Represents a managed, .NET assembly. - /// - [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] - public class ManagedLibrary - { - private ManagedLibrary(AssemblyName name, string additionalProbingPath, string appLocalPath) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); - AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); - } - - /// - /// Name of the managed library - /// - public AssemblyName Name { get; private set; } - - /// - /// Contains path to file within an additional probing path root. This is typically a combination - /// of the NuGet package ID (lowercased), version, and path within the package. - /// - /// For example, microsoft.data.sqlite/1.0.0/lib/netstandard1.3/Microsoft.Data.Sqlite.dll - /// - /// - public string AdditionalProbingPath { get; private set; } - - /// - /// Contains path to file within a deployed, framework-dependent application. - /// - /// For most managed libraries, this will be the file name. - /// For example, MyPlugin1.dll. - /// - /// - /// For runtime-specific managed implementations, this may include a sub folder path. - /// For example, runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll - /// - /// - public string AppLocalPath { get; private set; } - - /// - /// Create an instance of from a NuGet package. - /// - /// The name of the package. - /// The version of the package. - /// The path within the NuGet package. - /// - public static ManagedLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) - { - // When the asset comes from "lib/$tfm/", Microsoft.NET.Sdk will flatten this during publish based on the most compatible TFM. - // The SDK will not flatten managed libraries found under runtimes/ - var appLocalPath = assetPath.StartsWith("lib/") - ? Path.GetFileName(assetPath) - : assetPath; - - return new ManagedLibrary( - new AssemblyName(Path.GetFileNameWithoutExtension(assetPath)), - Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath), - appLocalPath - ); - } - } -} -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; - -namespace McMaster.NETCore.Plugins.LibraryModel -{ - /// - /// Represents an unmanaged library, such as `libsqlite3`, which may need to be loaded - /// for P/Invoke to work. - /// - [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] - public class NativeLibrary - { - private NativeLibrary(string name, string appLocalPath, string additionalProbingPath) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); - AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); - } - - /// - /// Name of the native library. This should match the name of the P/Invoke call. - /// - /// For example, if specifying `[DllImport("sqlite3")]`, should be sqlite3. - /// This may not match the exact file name as loading will attempt variations on the name according - /// to OS convention. On Windows, P/Invoke will attempt to load `sqlite3.dll`. On macOS, it will - /// attempt to find `sqlite3.dylib` and `libsqlite3.dylib`. On Linux, it will attempt to find - /// `sqlite3.so` and `libsqlite3.so`. - /// - /// - public string Name { get; private set; } - - /// - /// Contains path to file within a deployed, framework-dependent application - /// - /// For example, runtimes/linux-x64/native/libsqlite.so - /// - /// - public string AppLocalPath { get; private set; } - - /// - /// Contains path to file within an additional probing path root. This is typically a combination - /// of the NuGet package ID (lowercased), version, and path within the package. - /// - /// For example, sqlite/3.13.3/runtimes/linux-x64/native/libsqlite.so - /// - /// - public string AdditionalProbingPath { get; private set; } - - /// - /// Create an instance of from a NuGet package. - /// - /// The name of the package. - /// The version of the package. - /// The path within the NuGet package. - /// - public static NativeLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) - { - return new NativeLibrary( - Path.GetFileNameWithoutExtension(assetPath), - assetPath, - Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath) - ); - } - } -} // Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace McMaster.NETCore.Plugins.Internal -{ - internal class Debouncer : IDisposable - { - private readonly CancellationTokenSource _cts = new(); - private readonly TimeSpan _waitTime; - private int _counter; - - public Debouncer(TimeSpan waitTime) - { - _waitTime = waitTime; - } - - public void Execute(Action action) - { - var current = Interlocked.Increment(ref _counter); - - Task.Delay(_waitTime).ContinueWith(task => - { - // Is this the last task that was queued? - if (current == _counter && !_cts.IsCancellationRequested) - { - action(); - } - - task.Dispose(); - }, _cts.Token); - } - - public void Dispose() - { - _cts.Cancel(); - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace McMaster.NETCore.Plugins -{ - internal class PlatformInformation - { - public static readonly string[] NativeLibraryExtensions; - public static readonly string[] NativeLibraryPrefixes; - public static readonly string[] ManagedAssemblyExtensions = new[] - { - ".dll", - ".ni.dll", - ".exe", - ".ni.exe" - }; - - static PlatformInformation() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - NativeLibraryPrefixes = new[] { "" }; - NativeLibraryExtensions = new[] { ".dll" }; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - NativeLibraryPrefixes = new[] { "", "lib", }; - NativeLibraryExtensions = new[] { ".dylib" }; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - NativeLibraryPrefixes = new[] { "", "lib" }; - NativeLibraryExtensions = new[] { ".so", ".so.1" }; - } - else - { - Debug.Fail("Unknown OS type"); - NativeLibraryPrefixes = Array.Empty(); - NativeLibraryExtensions = Array.Empty(); - } - } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace McMaster.NETCore.Plugins -{ - internal class RuntimeConfig - { - public RuntimeOptions? runtimeOptions { get; set; } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace McMaster.NETCore.Plugins -{ - internal class RuntimeOptions - { - public string? Tfm { get; set; } - - public string[]? AdditionalProbingPaths { get; set; } - } -}// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Reflection; -using McMaster.NETCore.Plugins; -using Microsoft.AspNetCore.Mvc.ApplicationParts; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Extends the MVC builder. - /// - public static class MvcPluginExtensions - { - /// - /// Loads controllers and razor pages from a plugin assembly. - /// - /// This creates a loader with set to true. - /// If you need more control over shared types, use instead. - /// - /// - /// The MVC builder - /// Full path the main .dll file for the plugin. - /// The builder - public static IMvcBuilder AddPluginFromAssemblyFile(this IMvcBuilder mvcBuilder, string assemblyFile) - { - var plugin = PluginLoader.CreateFromAssemblyFile( - assemblyFile, // create a plugin from for the .dll file - config => - // this ensures that the version of MVC is shared between this app and the plugin - config.PreferSharedTypes = true); - - return mvcBuilder.AddPluginLoader(plugin); - } - - /// - /// Loads controllers and razor pages from a plugin loader. - /// - /// In order for this to work, the PluginLoader instance must be configured to share the types - /// and - /// (comes from Microsoft.AspNetCore.Mvc.Core.dll). The easiest way to ensure that is done correctly - /// is to set to true. - /// - /// - /// The MVC builder - /// An instance of PluginLoader. - /// The builder - public static IMvcBuilder AddPluginLoader(this IMvcBuilder mvcBuilder, PluginLoader pluginLoader) - { - var pluginAssembly = pluginLoader.LoadDefaultAssembly(); - - // This loads MVC application parts from plugin assemblies - var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly); - foreach (var part in partFactory.GetApplicationParts(pluginAssembly)) - { - mvcBuilder.PartManager.ApplicationParts.Add(part); - } - - // This piece finds and loads related parts, such as MvcAppPlugin1.Views.dll. - var relatedAssembliesAttrs = pluginAssembly.GetCustomAttributes(); - foreach (var attr in relatedAssembliesAttrs) - { - var assembly = pluginLoader.LoadAssembly(attr.AssemblyFileName); - partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); - foreach (var part in partFactory.GetApplicationParts(assembly)) - { - mvcBuilder.PartManager.ApplicationParts.Add(part); - } - } - - return mvcBuilder; - } - } -} -look at all previous code examples and chat messages and at below text and code and use it to improve the plugin system -This tutorial shows you how to create a custom AssemblyLoadContext to load plugins. An AssemblyDependencyResolver is used to resolve the dependencies of the plugin. The tutorial correctly isolates the plugin's dependencies from the hosting application. You'll learn how to: -Structure a project to support plugins. -Create a custom AssemblyLoadContext to load each plugin. -Use the System.Runtime.Loader.AssemblyDependencyResolver type to allow plugins to have dependencies. -Author plugins that can be easily deployed by just copying the build artifacts. -Prerequisites -Install the .NET 5 SDK or a newer version. - Note -The sample code targets .NET 5, but all the features it uses were introduced in .NET Core 3.0 and are available in all .NET releases since then. -Create the application -The first step is to create the application: -Create a new folder, and in that folder run the following command: -.NET CLICopy -dotnet new console -o AppWithPlugin - -To make building the project easier, create a Visual Studio solution file in the same folder. Run the following command: -.NET CLICopy -dotnet new sln - -Run the following command to add the app project to the solution: -.NET CLICopy -dotnet sln add AppWithPlugin/AppWithPlugin.csproj - -Now we can fill in the skeleton of our application. Replace the code in the AppWithPlugin/Program.cs file with the following code: -C#Copy -using PluginBase; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace AppWithPlugin -{ - class Program - { - static void Main(string[] args) - { - try - { - if (args.Length == 1 && args[0] == "/d") - { - Console.WriteLine("Waiting for any key..."); - Console.ReadLine(); - } - - // Load commands from plugins. - - if (args.Length == 0) - { - Console.WriteLine("Commands: "); - // Output the loaded commands. - } - else - { - foreach (string commandName in args) - { - Console.WriteLine($"-- {commandName} --"); - - // Execute the command with the name passed as an argument. - - Console.WriteLine(); - } - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - } -} - -Create the plugin interfaces -The next step in building an app with plugins is defining the interface the plugins need to implement. We suggest that you make a class library that contains any types that you plan to use for communicating between your app and plugins. This division allows you to publish your plugin interface as a package without having to ship your full application. -In the root folder of the project, run dotnet new classlib -o PluginBase. Also, run dotnet sln add PluginBase/PluginBase.csproj to add the project to the solution file. Delete the PluginBase/Class1.cs file, and create a new file in the PluginBase folder named ICommand.cs with the following interface definition: -C#Copy -namespace PluginBase -{ - public interface ICommand - { - string Name { get; } - string Description { get; } - - int Execute(); - } -} - -This ICommand interface is the interface that all of the plugins will implement. -Now that the ICommand interface is defined, the application project can be filled in a little more. Add a reference from the AppWithPlugin project to the PluginBase project with the dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj command from the root folder. -Replace the // Load commands from plugins comment with the following code snippet to enable it to load plugins from given file paths: -C#Copy -string[] pluginPaths = new string[] -{ - // Paths to plugins to load. -}; - -IEnumerable commands = pluginPaths.SelectMany(pluginPath => -{ - Assembly pluginAssembly = LoadPlugin(pluginPath); - return CreateCommands(pluginAssembly); -}).ToList(); - -Then replace the // Output the loaded commands comment with the following code snippet: -C#Copy -foreach (ICommand command in commands) -{ - Console.WriteLine($"{command.Name}\t - {command.Description}"); -} - -Replace the // Execute the command with the name passed as an argument comment with the following snippet: -C#Copy -ICommand command = commands.FirstOrDefault(c => c.Name == commandName); -if (command == null) -{ - Console.WriteLine("No such command is known."); - return; -} - -command.Execute(); - -And finally, add static methods to the Program class named LoadPlugin and CreateCommands, as shown here: -C#Copy -static Assembly LoadPlugin(string relativePath) -{ - throw new NotImplementedException(); -} - -static IEnumerable CreateCommands(Assembly assembly) -{ - int count = 0; - - foreach (Type type in assembly.GetTypes()) - { - if (typeof(ICommand).IsAssignableFrom(type)) - { - ICommand result = Activator.CreateInstance(type) as ICommand; - if (result != null) - { - count++; - yield return result; - } - } - } - - if (count == 0) - { - string availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName)); - throw new ApplicationException( - $"Can't find any type which implements ICommand in {assembly} from {assembly.Location}.\n" + - $"Available types: {availableTypes}"); - } -} - -Load plugins -Now the application can correctly load and instantiate commands from loaded plugin assemblies, but it's still unable to load the plugin assemblies. Create a file named PluginLoadContext.cs in the AppWithPlugin folder with the following contents: -C#Copy -using System; -using System.Reflection; -using System.Runtime.Loader; - -namespace AppWithPlugin -{ - class PluginLoadContext : AssemblyLoadContext - { - private AssemblyDependencyResolver _resolver; - - public PluginLoadContext(string pluginPath) - { - _resolver = new AssemblyDependencyResolver(pluginPath); - } - - protected override Assembly Load(AssemblyName assemblyName) - { - string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); - if (assemblyPath != null) - { - return LoadFromAssemblyPath(assemblyPath); - } - - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - - return IntPtr.Zero; - } - } -} - -The PluginLoadContext type derives from AssemblyLoadContext. The AssemblyLoadContext type is a special type in the runtime that allows developers to isolate loaded assemblies into different groups to ensure that assembly versions don't conflict. Additionally, a custom AssemblyLoadContext can choose different paths to load assemblies from and override the default behavior. The PluginLoadContext uses an instance of the AssemblyDependencyResolver type introduced in .NET Core 3.0 to resolve assembly names to paths. The AssemblyDependencyResolver object is constructed with the path to a .NET class library. It resolves assemblies and native libraries to their relative paths based on the .deps.json file for the class library whose path was passed to the AssemblyDependencyResolver constructor. The custom AssemblyLoadContext enables plugins to have their own dependencies, and the AssemblyDependencyResolver makes it easy to correctly load the dependencies. -Now that the AppWithPlugin project has the PluginLoadContext type, update the Program.LoadPlugin method with the following body: -C#Copy -static Assembly LoadPlugin(string relativePath) -{ - // Navigate up to the solution root - string root = Path.GetFullPath(Path.Combine( - Path.GetDirectoryName( - Path.GetDirectoryName( - Path.GetDirectoryName( - Path.GetDirectoryName( - Path.GetDirectoryName(typeof(Program).Assembly.Location))))))); - - string pluginLocation = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\', Path.DirectorySeparatorChar))); - Console.WriteLine($"Loading commands from: {pluginLocation}"); - PluginLoadContext loadContext = new PluginLoadContext(pluginLocation); - return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation))); -} - -By using a different PluginLoadContext instance for each plugin, the plugins can have different or even conflicting dependencies without issue. -Simple plugin with no dependencies -Back in the root folder, do the following: -Run the following command to create a new class library project named HelloPlugin: -.NET CLICopy -dotnet new classlib -o HelloPlugin - -Run the following command to add the project to the AppWithPlugin solution: -.NET CLICopy -dotnet sln add HelloPlugin/HelloPlugin.csproj - -Replace the HelloPlugin/Class1.cs file with a file named HelloCommand.cs with the following contents: -C#Copy -using PluginBase; -using System; - -namespace HelloPlugin -{ - public class HelloCommand : ICommand - { - public string Name { get => "hello"; } - public string Description { get => "Displays hello message."; } - - public int Execute() - { - Console.WriteLine("Hello !!!"); - return 0; - } - } -} - -Now, open the HelloPlugin.csproj file. It should look similar to the following: -XMLCopy - - - - net5.0 - - - - - -In between the  tags, add the following element: -XMLCopy - true - -The true prepares the project so that it can be used as a plugin. Among other things, this will copy all of its dependencies to the output of the project. For more details see EnableDynamicLoading. -In between the  tags, add the following elements: -XMLCopy - - - false - runtime - - - -The false element is important. This tells MSBuild to not copy PluginBase.dll to the output directory for HelloPlugin. If the PluginBase.dll assembly is present in the output directory, PluginLoadContext will find the assembly there and load it when it loads the HelloPlugin.dll assembly. At this point, the HelloPlugin.HelloCommand type will implement the ICommand interface from the PluginBase.dll in the output directory of the HelloPlugin project, not the ICommand interface that is loaded into the default load context. Since the runtime sees these two types as different types from different assemblies, the AppWithPlugin.Program.CreateCommands method won't find the commands. As a result, the false metadata is required for the reference to the assembly containing the plugin interfaces. -Similarly, the runtime element is also important if the PluginBase references other packages. This setting has the same effect as false but works on package references that the PluginBase project or one of its dependencies may include. -Now that the HelloPlugin project is complete, you should update the AppWithPlugin project to know where the HelloPlugin plugin can be found. After the // Paths to plugins to load comment, add @"HelloPlugin\bin\Debug\net5.0\HelloPlugin.dll" (this path could be different based on the .NET Core version you use) as an element of the pluginPaths array. -Plugin with library dependencies -Almost all plugins are more complex than a simple "Hello World", and many plugins have dependencies on other libraries. The JsonPlugin and OldJsonPlugin projects in the sample show two examples of plugins with NuGet package dependencies on Newtonsoft.Json. Because of this, all plugin projects should add true to the project properties so that they copy all of their dependencies to the output of dotnet build. Publishing the class library with dotnet publish will also copy all of its dependencies to the publish output. -Other examples in the sample -The complete source code for this tutorial can be found in the dotnet/samples repository. The completed sample includes a few other examples of AssemblyDependencyResolver behavior. For example, the AssemblyDependencyResolver object can also resolve native libraries as well as localized satellite assemblies included in NuGet packages. The UVPlugin and FrenchPlugin in the samples repository demonstrate these scenarios. -Reference a plugin interface from a NuGet package -Let's say that there is an app A that has a plugin interface defined in the NuGet package named A.PluginBase. How do you reference the package correctly in your plugin project? For project references, using the false metadata on the ProjectReference element in the project file prevented the dll from being copied to the output. -To correctly reference the A.PluginBase package, you want to change the  element in the project file to the following: -XMLCopy - - runtime - - -This prevents the A.PluginBase assemblies from being copied to the output directory of your plugin and ensures that your plugin will use A's version of A.PluginBase. -Plugin target framework recommendations -Because plugin dependency loading uses the .deps.json file, there is a gotcha related to the plugin's target framework. Specifically, your plugins should target a runtime, such as .NET 5, instead of a version of .NET Standard. The .deps.json file is generated based on which framework the project targets, and since many .NET Standard-compatible packages ship reference assemblies for building against .NET Standard and implementation assemblies for specific runtimes, the .deps.json may not correctly see implementation assemblies, or it may grab the .NET Standard version of an assembly instead of the .NET Core version you expect. -Plugin framework references -Currently, plugins can't introduce new frameworks into the process. For example, you can't load a plugin that uses the Microsoft.AspNetCore.App framework into an application that only uses the root Microsoft.NETCore.App framework. The host application must declare references to all frameworks needed by plugins. -Use above tutorial as basic idea - The interface should include plugin name , plugin description , which app or game launch the plugin targets , the app description , and any dependencies the plugin itself may have -plugins must load from a separate plugins directory , have a api interface available -Loading Assemblies at Runtime -Now, we must code the plugin behavior we want to provide to our users. Let’s create the class: -Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE! - -public class PluginLoadContext : AssemblyLoadContext -{ -private AssemblyDependencyResolver _resolver; -public PluginLoadContext(string pluginPath) -{ -_resolver = new AssemblyDependencyResolver(pluginPath); -} -} -Here, we create the PluginLoadContext class that inherits from AssemblyLoadContext, which allows us to work with plugin assemblies at runtime. -Now in the Program class, we can create a new method to do the assembly loading part: - -static Assembly LoadPlugin(string assemblyPath) -{ -Console.WriteLine($"Loading commands from: {assemblyPath}"); -var loadContext = new PluginLoadContext(assemblyPath); -return loadContext.LoadFromAssemblyName( -new AssemblyName(Path.GetFileNameWithoutExtension(assemblyPath))); -} -Here, we define the LoadPlugin() method that takes an assemblyPath as a parameter. With this, we create a new instance of our PluginLoadContext class, providing the assemblyPath. Finally, we call the LoadFromAssemblyName() method, constructing an AssemblyName object from assemblyName, returning the assembly. -Next, we will look at defining an interface that all plugins must implement so that they may be used in our host application. -Defining the Plugin Interface -Firstly, we create a new class library that holds our plugin interface. In our case, the PluginBase project contains the ICommand plugin interface. We need to write an interface that properly provides our host application with everything required to run the plugin. This interface will be different for every application. -Let’s create one for our application: - -public interface ICommand -{ -string Name { get; } -string Description { get; } -int Invoke(); -} -The ICommand interface has three members: the Name and Description properties, and the Invoke() method. The Name and Description members are used to display to the user when we print the weather app plugins available for use. Finally, the Invoke() method is what allows us to run the plugin. -Creating a Plugin -Let’s start creating our plugin by creating a new class library that produces our plugin assembly: -Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE! - - -dotnet new classlib -o TemperatureCommands - -Next, let’s edit our .csproj file for this app: - - -true - -In the existing  we enable . This setting constructs the assembly, enabling its use as a plugin. -Further, we edit the project reference to the PluginBase class library: - - - -false -runtime - - -Firstly, the  setting tells the build to not make a copy of PluginBase.dll in the output directory. Lastly,  works in the same way but for dependencies of the PluginBase assembly. -Now we can implement our plugin! Let’s begin by implementing the ICommand interface from the PluginBase project we created in the last section: - -public class TemperatureCommand : ICommand -{ -public TemperatureCommand(); -public string Name { get => "temperature"; } -public string Description { get => "Displays high and low temperatures for the users location."; } -public int Invoke() -{ -Console.WriteLine("In your area, there will be high of 84F and a low of 69F."); -return 0; -} -} -Here, we implement the Invoke() method, which serves as the entry point to the plugin. For this article, we simply log the weather data to the console. -Next, let’s tie all these pieces together as we load all plugins and give the user the option to choose a command. -Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE! -Running the Plugin From the Base Solution -Returning to the WeatherAppHost project, let’s implement the Main() method: - -public static void Main(string[] args) -{ -try -{ -var binDir = Environment.CurrentDirectory; -var files = Directory.GetFiles(binDir, "*.dll").ToList(); -files.Remove(typeof(Program).Assembly.Location); -files.Remove(Path.Combine(binDir, "PlugInBase.dll")); -var commands = files.SelectMany(pluginPath => -{ -var pluginAssembly = LoadPlugin(pluginPath); -return CreateCommands(pluginAssembly); -}).ToList(); -Console.WriteLine("Welcome to the Weather App."); -foreach (string commandName in args) -{ -Console.WriteLine($"-- {commandName} --"); -var command = commands.FirstOrDefault(c => c.Name == commandName); -if (command == null) -{ -Console.WriteLine(); -Console.WriteLine("No such command is known."); -return; -} -command.Invoke(); -} -Console.WriteLine("\nApplication Closing"); -} -catch (Exception ex) -{ -Console.WriteLine(ex); -} -} -In the Main() method, we search for all assemblies in our build directory, filter out the host app and PluginBase assemblies, and load the remaining assemblies. From them, we get the ICommand objects which we can call Invoke() based on the command line argument array args. -The CreateCommands() method takes an Assembly object and creates an instance of all classes that implement ICommand: - -static IEnumerable CreateCommands(Assembly assembly) -{ -var count = 0; -foreach (var type in assembly.GetTypes()) -{ -if (type is not null && -type.GetInterfaces().Any(intf => intf.FullName?.Contains(nameof(ICommand)) ?? false)) -{ -var result = Activator.CreateInstance(type) as ICommand; -if (result != null) -{ -count++; -yield return result; -} -} -} -if (count == 0) -{ -var availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName)); -throw new ApplicationException( -$"Can't find any type which implements ICommand in {assembly} from {assembly.Location}.\n" + -$"Available types: {availableTypes}"); -} -} -Here, we accept a plugin Assembly object that is produced from the LoadPlugin() method as an argument. We then iterate over all Type objects contained in the assembly returned by the GetTypes() method, checking if each implements the ICommand interface. If so, we create an instance of this type as an ICommand object. Finally, we return this ICommand object. To sum up, this method returns ICommand objects for all types that implement ICommand in the given assembly. -In the end, users can start the application and choose from a list of commands presented to them. Again, remember that each of these commands corresponds to an individual plugin assembly separate from our host app.  -Let’s take a look at the output of running the temperature command: - -Welcome to the Weather App. --- temperature -- -In your area, there will be high of 84F and a low of 69F. -Application Closing -Finally, now that we understand the general concept of a Plugin architecture and have developed a simple application, let’s discuss the pros and cons of enabling the plugin functionality in our apps. -have xml comments inside each code piece -obviously expand with anything else functionality wise that may be needed in WebLaunch project -for example one such thing to convert to a plugin would be Class1.cs which handles loading spellborn and ffxiv -The plugin interface must be generic , and call functions from secondary libraries and be robust and thread safe -First, let's create the core interfaces in CoreFunctions.cs: - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; - -namespace El_Garnan_Plugin_Loader -{ - public interface IGameLauncherPlugin - { - string Name { get; } - string GameId { get; } - Version Version { get; } - Task LaunchGameAsync(Dictionary launchParameters); - } - - public class PluginLoadContext : AssemblyLoadContext - { - private AssemblyDependencyResolver _resolver; - - public PluginLoadContext(string pluginPath) - { - _resolver = new AssemblyDependencyResolver(pluginPath); - } - - protected override Assembly Load(AssemblyName assemblyName) - { - string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); - if (assemblyPath != null) - { - return LoadFromAssemblyPath(assemblyPath); - } - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - return IntPtr.Zero; - } - } - - public class PluginManager - { - private readonly string _pluginsPath; - private readonly Dictionary _loadedPlugins = new(); - - public PluginManager(string pluginsPath) - { - _pluginsPath = pluginsPath; - } - - public void LoadPlugins() - { - if (!Directory.Exists(_pluginsPath)) - { - Directory.CreateDirectory(_pluginsPath); - } - - foreach (string pluginPath in Directory.GetFiles(_pluginsPath, "*.dll")) - { - var loadContext = new PluginLoadContext(pluginPath); - var assembly = loadContext.LoadFromAssemblyPath(pluginPath); - - foreach (Type type in assembly.GetTypes()) - { - if (typeof(IGameLauncherPlugin).IsAssignableFrom(type) && !type.IsAbstract) - { - var plugin = (IGameLauncherPlugin)Activator.CreateInstance(type); - if (plugin != null) - { - _loadedPlugins[plugin.GameId] = plugin; - } - } - } - } - } - - public IGameLauncherPlugin GetPlugin(string gameId) - { - return _loadedPlugins.TryGetValue(gameId, out var plugin) - ? plugin - : throw new KeyNotFoundException($"Plugin for game {gameId} not found"); - } - } -} - -Copy - -Apply - -Now let's modify Program.cs to use the plugin system: - -// Add to existing using statements -using El_Garnan_Plugin_Loader; - -class Program -{ - private static readonly PluginManager _pluginManager; - - static Program() - { - string pluginsPath = Path.Combine(GetInstallPath(), "Plugins"); - _pluginManager = new PluginManager(pluginsPath); - _pluginManager.LoadPlugins(); - } - - // Modify the existing FFXIV launch code to use plugins - private static async Task HandleGameLaunch(string[] args) - { - try - { - var plugin = _pluginManager.GetPlugin("FFXIV"); - var launchParams = ParseLaunchParameters(args[0]); - - var process = await plugin.LaunchGameAsync(launchParams); - if (process != null) - { - // Existing process handling code - } - } - catch (Exception ex) - { - LogDebug($"Error launching game: {ex.Message}"); - ShowNotification("Error", "Failed to launch game"); - } - } - - private static Dictionary ParseLaunchParameters(string args) - { - // Parse URL parameters into dictionary - var parameters = new Dictionary(); - // Add parsing logic here - return parameters; - } -} - -Copy - -Apply - -Example FFXIV plugin implementation: - -public class FFXIVPlugin : IGameLauncherPlugin -{ - public string Name => "Final Fantasy XIV"; - public string GameId => "FFXIV"; - public Version Version => new Version(1, 0, 0); - - public async Task LaunchGameAsync(Dictionary parameters) - { - // Existing FFXIV launch logic goes here - return await networklogic.LaunchGameAsync( - parameters["gamePath"], - parameters["sid"], - int.Parse(parameters["language"]), - bool.Parse(parameters["dx11"]), - int.Parse(parameters["expansionLevel"]), - bool.Parse(parameters["isSteam"]), - int.Parse(parameters["region"])); - } -} - -Copy - -Apply - -This implementation: - -Creates a plugin system with clean interfaces -Supports dynamic loading of game-specific plugins -Handles assembly loading and dependencies properly -Maintains isolation between plugins -Makes it easy to add new game launchers -Each game launcher can be implemented as a separate plugin DLL that implements IGameLauncherPlugin. -Yes, you're absolutely right! Let's modify the plugin system to use dynamic loading. Here's the improved implementation: - -using System.Reflection; -using System.Runtime.Loader; - -namespace El_Garnan_Plugin_Loader -{ - public class CoreFunctions - { - private readonly Dictionary _loadedPlugins = new(); - private readonly string _pluginsPath; - - public CoreFunctions(string pluginsPath) - { - _pluginsPath = pluginsPath; - } - - public async Task LoadPlugins() - { - foreach (string pluginPath in Directory.GetFiles(_pluginsPath, "*.dll")) - { - try - { - var pluginAssembly = LoadPlugin(pluginPath); - var pluginTypes = GetPluginTypes(pluginAssembly); - - foreach (Type type in pluginTypes) - { - if (Activator.CreateInstance(type) is IGamePlugin plugin) - { - await plugin.Initialize(); - _loadedPlugins[plugin.PluginId] = plugin; - } - } - } - catch (Exception ex) - { - // Log plugin load failure - Console.WriteLine($"Failed to load plugin {pluginPath}: {ex.Message}"); - } - } - } - - private Assembly LoadPlugin(string pluginPath) - { - var loadContext = new PluginLoadContext(pluginPath); - return loadContext.LoadFromAssemblyPath(pluginPath); - } - - private IEnumerable GetPluginTypes(Assembly assembly) - { - foreach (Type type in assembly.GetTypes()) - { - if (typeof(IGamePlugin).IsAssignableFrom(type) && !type.IsAbstract) - { - yield return type; - } - } - } - - public IGamePlugin? GetPlugin(string pluginId) - { - return _loadedPlugins.TryGetValue(pluginId, out var plugin) ? plugin : null; - } - } - - class PluginLoadContext : AssemblyLoadContext - { - private AssemblyDependencyResolver _resolver; - - public PluginLoadContext(string pluginPath) - { - _resolver = new AssemblyDependencyResolver(pluginPath); - } - - protected override Assembly? Load(AssemblyName assemblyName) - { - string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); - if (assemblyPath != null) - { - return LoadFromAssemblyPath(assemblyPath); - } - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - return IntPtr.Zero; - } - } -} - -Copy - -Apply - -This implementation: - -Uses AssemblyLoadContext for proper plugin isolation -Implements dynamic loading of assemblies and dependencies -Handles unmanaged DLLs properly -Provides clean plugin lifecycle management - - - -Claude 3.5 Sonnet (New) -I'll create the integration code for Program.cs to use the new plugin system: - -// Add to existing using statements -using El_Garnan_Plugin_Loader; - -// Inside Program class, add: -private static CoreFunctions _pluginSystem; - -// Modify the Main method to initialize plugin system -static void Main(string[] args) -{ - var handle = GetConsoleWindow(); - ShowWindow(handle, SW_HIDE); - - // Initialize plugin system - string pluginsPath = Path.Combine(GetInstallPath(), "Plugins"); - Directory.CreateDirectory(pluginsPath); - _pluginSystem = new CoreFunctions(pluginsPath); - - // Load plugins at startup - await _pluginSystem.LoadPlugins(); - - // Modify the existing args handling to use plugins - if (args[0].Contains("?ffxivhandle=yes")) - { - var ffxivPlugin = _pluginSystem.GetPlugin("ffxiv-launch"); - if (ffxivPlugin != null) - { - var parameters = ParseLaunchParameters(args[0]); - await ffxivPlugin.LaunchGame(parameters); - } - } - else if (args[0].Contains("?spellbornhandle=yes")) - { - var spellbornPlugin = _pluginSystem.GetPlugin("spellborn-launch"); - if (spellbornPlugin != null) - { - var parameters = new Dictionary - { - ["args"] = args[0] - }; - await spellbornPlugin.LaunchGame(parameters); - } - } -} - -// Add helper method to parse launch parameters -private static Dictionary ParseLaunchParameters(string args) -{ - var parameters = new Dictionary(); - var pairs = args.Split('&'); - - foreach (var pair in pairs) - { - var keyValue = pair.Split('='); - if (keyValue.Length == 2) - { - parameters[keyValue[0]] = keyValue[1]; - } - } - - return parameters; -} - -Copy - -Apply - -Would you like me to show the plugin deployment structure or explain any specific part of the integration? - -Try again with different context -Add context... -Avatar for rsgeeens-ctijr -1 sd -yes - - - -Claude 3.5 Sonnet (New) -I'll show the plugin deployment structure and add the necessary build configurations: - - - - net9.0-windows - true - true - - - - - false - runtime - - - - -Copy - -Apply - -El'GarnanPluginSystem\El'GarnanPluginSystem.csproj -And here's how to set up the plugin deployment in the main application: - -private static void DeployPlugins() -{ - string pluginsPath = Path.Combine(GetInstallPath(), "Plugins"); - - // Create plugins directory structure - var pluginDirs = new[] - { - Path.Combine(pluginsPath, "FFXIV"), - Path.Combine(pluginsPath, "Spellborn") - }; - - foreach (var dir in pluginDirs) - { - Directory.CreateDirectory(dir); - } - - // Copy plugin files on first run - CopyPluginFiles(pluginsPath); -} - -private static void CopyPluginFiles(string pluginsRoot) -{ - var pluginSources = new Dictionary - { - ["FFXIV"] = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins", "FFXIV"), - ["Spellborn"] = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins", "Spellborn") - }; - - foreach (var plugin in pluginSources) - { - if (Directory.Exists(plugin.Value)) - { - var targetDir = Path.Combine(pluginsRoot, plugin.Key); - foreach (var file in Directory.GetFiles(plugin.Value, "*.dll")) - { - var targetFile = Path.Combine(targetDir, Path.GetFileName(file)); - File.Copy(file, targetFile, true); - } - } - } -} - -Copy - -Apply - -This sets up a clean plugin deployment structure and ensures plugins are properly copied to their respective directories. Would you like me to show the logging integration or explain any other aspects? -Yes, I can help create a robust plugin system for WebLaunch. Here's how we can structure the core plugin interface and loader: - -using System; -using System.Threading.Tasks; - -namespace El_Garnan_Plugin_Loader.Interfaces -{ - /// - /// Core interface that all game launch plugins must implement - /// - public interface IGamePlugin - { - /// - /// Unique identifier for the plugin - /// - string PluginId { get; } - - /// - /// Display name of the plugin - /// - string Name { get; } - - /// - /// Plugin description - /// - string Description { get; } - - /// - /// Target game/application this plugin handles - /// - string TargetApplication { get; } - - /// - /// Plugin version - /// - Version Version { get; } - - /// - /// Initializes the plugin - /// - Task Initialize(); - - /// - /// Launches the target application with given parameters - /// - Task LaunchGame(IDictionary launchParameters); - - /// - /// Cleanup plugin resources - /// - Task Shutdown(); - } -} - -Copy - -Apply - -using System.Reflection; -using System.Runtime.Loader; - -namespace El_Garnan_Plugin_Loader -{ - public class CoreFunctions - { - private readonly string _pluginsPath; - private readonly Dictionary _loadedPlugins; - private readonly AssemblyLoadContext _loadContext; - - public CoreFunctions(string pluginsPath) - { - _pluginsPath = pluginsPath; - _loadedPlugins = new Dictionary(); - _loadContext = new PluginLoadContext(pluginsPath); - } - - /// - /// Loads all plugins from the plugins directory - /// - public async Task LoadPlugins() - { - var pluginFiles = Directory.GetFiles(_pluginsPath, "*.dll"); - - foreach(var file in pluginFiles) - { - try - { - var assembly = _loadContext.LoadFromAssemblyPath(file); - var plugins = CreatePlugins(assembly); - - foreach(var plugin in plugins) - { - await plugin.Initialize(); - _loadedPlugins[plugin.PluginId] = plugin; - } - } - catch(Exception ex) - { - // Log plugin load failure - } - } - } - - /// - /// Gets a loaded plugin by ID - /// - public IGamePlugin GetPlugin(string pluginId) - { - return _loadedPlugins.TryGetValue(pluginId, out var plugin) ? plugin : null; - } - - private IEnumerable CreatePlugins(Assembly assembly) - { - foreach(var type in assembly.GetTypes()) - { - if(typeof(IGamePlugin).IsAssignableFrom(type) && !type.IsAbstract) - { - if(Activator.CreateInstance(type) is IGamePlugin plugin) - { - yield return plugin; - } - } - } - } - } -} - -Copy - -Apply - -This provides a foundation for the plugin system. Would you like me to continue with implementation examples for FFXIV and Spellborn plugins, or would you like more details about any specific part? - -Try again with different context -Public knowledge only -Current file only -Add context... -Avatar for rsgeeens-ctijr -1 sd -yes - - - -Claude 3.5 Sonnet (New) -I'll create the FFXIV and Spellborn plugin implementations that integrate with the existing codebase: - -using CoreLibLaunchSupport; -using El_Garnan_Plugin_Loader.Interfaces; - -namespace El_Garnan_Plugin_Loader.Plugins -{ - public class FFXIVPlugin : IGamePlugin - { - private networklogic _networkHandler; - private launchers _launcher; - - public string PluginId => "ffxiv-launch"; - public string Name => "Final Fantasy XIV Launcher"; - public string Description => "Handles launching of FFXIV with login credentials"; - public string TargetApplication => "Final Fantasy XIV"; - public Version Version => new Version(1, 0, 0); - - public async Task Initialize() - { - _networkHandler = new networklogic(); - _launcher = new launchers(); - } - - public async Task LaunchGame(IDictionary parameters) - { - try - { - var sid = _networkHandler.GetRealSid( - parameters["gamePath"], - parameters["username"], - parameters["password"], - parameters.GetValueOrDefault("otp", ""), - bool.Parse(parameters.GetValueOrDefault("isSteam", "false")) - ); - - if (sid == "BAD") - return false; - - var process = await _networkHandler.LaunchGameAsync( - parameters["gamePath"], - sid, - int.Parse(parameters.GetValueOrDefault("language", "1")), - true, // dx11 - int.Parse(parameters.GetValueOrDefault("expansionLevel", "6")), - bool.Parse(parameters.GetValueOrDefault("isSteam", "false")), - int.Parse(parameters.GetValueOrDefault("region", "3")) - ); - - return process != null; - } - catch - { - return false; - } - } - - public Task Shutdown() - { - return Task.CompletedTask; - } - } -} - -Copy - -Apply - -using CoreLibLaunchSupport; -using El_Garnan_Plugin_Loader.Interfaces; - -namespace El_Garnan_Plugin_Loader.Plugins -{ - public class SpellbornPlugin : IGamePlugin - { - private SpellbornSupporter _supporter; - - public string PluginId => "spellborn-launch"; - public string Name => "Chronicles of Spellborn Launcher"; - public string Description => "Handles launching of Chronicles of Spellborn"; - public string TargetApplication => "Chronicles of Spellborn"; - public Version Version => new Version(1, 0, 0); - - public Task Initialize() - { - _supporter = new SpellbornSupporter(); - return Task.CompletedTask; - } - - public async Task LaunchGame(IDictionary parameters) - { - try - { - string gamePath = _supporter.GetGamePathFromArgs(parameters["args"].Split(' ')); - Directory.SetCurrentDirectory(gamePath); - _supporter.StartupRoutine(parameters["args"].Split(' ')); - return true; - } - catch - { - return false; - } - } - - public Task Shutdown() - { - return Task.CompletedTask; - } - } -} - -Copy - -Apply - -These implementations integrate the existing launch logic into the plugin system while providing a clean interface. The plugins can be loaded dynamically and handle their specific game launch requirements through the unified IGamePlugin interface. - -Would you like me to show how to integrate this into the main Program.cs or explain any specific part in more detail? -Yes, I can help create a robust plugin system for WebLaunch. Here's how we can structure the core plugin interface and loader: - -using System; -using System.Threading.Tasks; - -namespace El_Garnan_Plugin_Loader.Interfaces -{ - /// - /// Core interface that all game launch plugins must implement - /// - public interface IGamePlugin - { - /// - /// Unique identifier for the plugin - /// - string PluginId { get; } - - /// - /// Display name of the plugin - /// - string Name { get; } - - /// - /// Plugin description - /// - string Description { get; } - - /// - /// Target game/application this plugin handles - /// - string TargetApplication { get; } - - /// - /// Plugin version - /// - Version Version { get; } - - /// - /// Initializes the plugin - /// - Task Initialize(); - - /// - /// Launches the target application with given parameters - /// - Task LaunchGame(IDictionary launchParameters); - - /// - /// Cleanup plugin resources - /// - Task Shutdown(); - } -} - -Copy - -Apply - -using System.Reflection; -using System.Runtime.Loader; - -namespace El_Garnan_Plugin_Loader -{ - public class CoreFunctions - { - private readonly string _pluginsPath; - private readonly Dictionary _loadedPlugins; - private readonly AssemblyLoadContext _loadContext; - - public CoreFunctions(string pluginsPath) - { - _pluginsPath = pluginsPath; - _loadedPlugins = new Dictionary(); - _loadContext = new PluginLoadContext(pluginsPath); - } - - /// - /// Loads all plugins from the plugins directory - /// - public async Task LoadPlugins() - { - var pluginFiles = Directory.GetFiles(_pluginsPath, "*.dll"); - - foreach(var file in pluginFiles) - { - try - { - var assembly = _loadContext.LoadFromAssemblyPath(file); - var plugins = CreatePlugins(assembly); - - foreach(var plugin in plugins) - { - await plugin.Initialize(); - _loadedPlugins[plugin.PluginId] = plugin; - } - } - catch(Exception ex) - { - // Log plugin load failure - } - } - } - - /// - /// Gets a loaded plugin by ID - /// - public IGamePlugin GetPlugin(string pluginId) - { - return _loadedPlugins.TryGetValue(pluginId, out var plugin) ? plugin : null; - } - - private IEnumerable CreatePlugins(Assembly assembly) - { - foreach(var type in assembly.GetTypes()) - { - if(typeof(IGamePlugin).IsAssignableFrom(type) && !type.IsAbstract) - { - if(Activator.CreateInstance(type) is IGamePlugin plugin) - { - yield return plugin; - } - } - } - } - } -} - -Copy - -Apply - -This provides a foundation for the plugin system. Would you like me to continue with implementation examples for FFXIV and Spellborn plugins, or would you like more details about any specific part? - -Try again with different context -Public knowledge only -Current file only -Add context... -Avatar for rsgeeens-ctijr -1 sd -yes - - - -Claude 3.5 Sonnet (New) -I'll create the FFXIV and Spellborn plugin implementations that integrate with the existing codebase: - -using CoreLibLaunchSupport; -using El_Garnan_Plugin_Loader.Interfaces; - -namespace El_Garnan_Plugin_Loader.Plugins -{ - public class FFXIVPlugin : IGamePlugin - { - private networklogic _networkHandler; - private launchers _launcher; - - public string PluginId => "ffxiv-launch"; - public string Name => "Final Fantasy XIV Launcher"; - public string Description => "Handles launching of FFXIV with login credentials"; - public string TargetApplication => "Final Fantasy XIV"; - public Version Version => new Version(1, 0, 0); - - public async Task Initialize() - { - _networkHandler = new networklogic(); - _launcher = new launchers(); - } - - public async Task LaunchGame(IDictionary parameters) - { - try - { - var sid = _networkHandler.GetRealSid( - parameters["gamePath"], - parameters["username"], - parameters["password"], - parameters.GetValueOrDefault("otp", ""), - bool.Parse(parameters.GetValueOrDefault("isSteam", "false")) - ); - - if (sid == "BAD") - return false; - - var process = await _networkHandler.LaunchGameAsync( - parameters["gamePath"], - sid, - int.Parse(parameters.GetValueOrDefault("language", "1")), - true, // dx11 - int.Parse(parameters.GetValueOrDefault("expansionLevel", "6")), - bool.Parse(parameters.GetValueOrDefault("isSteam", "false")), - int.Parse(parameters.GetValueOrDefault("region", "3")) - ); - - return process != null; - } - catch - { - return false; - } - } - - public Task Shutdown() - { - return Task.CompletedTask; - } - } -} - -Copy - -Apply - -using CoreLibLaunchSupport; -using El_Garnan_Plugin_Loader.Interfaces; - -namespace El_Garnan_Plugin_Loader.Plugins -{ - public class SpellbornPlugin : IGamePlugin - { - private SpellbornSupporter _supporter; - - public string PluginId => "spellborn-launch"; - public string Name => "Chronicles of Spellborn Launcher"; - public string Description => "Handles launching of Chronicles of Spellborn"; - public string TargetApplication => "Chronicles of Spellborn"; - public Version Version => new Version(1, 0, 0); - - public Task Initialize() - { - _supporter = new SpellbornSupporter(); - return Task.CompletedTask; - } - - public async Task LaunchGame(IDictionary parameters) - { - try - { - string gamePath = _supporter.GetGamePathFromArgs(parameters["args"].Split(' ')); - Directory.SetCurrentDirectory(gamePath); - _supporter.StartupRoutine(parameters["args"].Split(' ')); - return true; - } - catch - { - return false; - } - } - - public Task Shutdown() - { - return Task.CompletedTask; - } - } -} - -Copy - -Apply - -These implementations integrate the existing launch logic into the plugin system while providing a clean interface. The plugins can be loaded dynamically and handle their specific game launch requirements through the unified IGamePlugin interface. - -Would you like me to show how to integrate this into the main Program.cs or explain any specific part in more detail? -using System; -class FailSoftArray { -int[] a; // ссылка на базовый массив -public int Length; // открытая переменная длины массива -308 Часть I. Язык C# -public bool ErrFlag; // обозначает результат последней операции -// Построить массив заданного размера. -public FailSoftArray(int size) { -a = new int[size]; -Length = size; -} -// Это индексатор типа int для массива FailSoftArray. -public int this[int index] { -// Это аксессор get. -get { -if(ok(index)) { -ErrFlag = false; -return a[index]; -} else { -ErrFlag = true; -}return 0; -} -// Это аксессор set. -set { -if(ok(index)) { -a[index] = value; -}ErrFlag = false; -}else ErrFlag = true; -} -/* Это еще один индексатор для массива FailSoftArray. -Он округляет свой аргумент до ближайшего целого индекса. */ -public int this[double idx] { -// Это аксессор get. -get { -int index; -// Округлить до ближайшего целого. -if( (idx - (int) idx) < 0.5) index = (int) idx; -else index = (int) idx + 1; -if(ok(index)) { -ErrFlag = false; -return a[index]; -} else { -ErrFlag = true; -}return 0; -} -// Это аксессор set. -set { -int index; -Глава 10. Индексаторы и свойства 309 -// Округлить до ближайшего целого. -if( (idx - (int) idx) < 0.5) index = (int) idx; -else index = (int) idx + 1; -if(ok(index)) { -a[index] = value; -}ErrFlag = false; -}else ErrFlag = true; -} -// Возвратить логическое значение true, если -// индекс находится в установленных границах. -private bool ok(int index) { -if (index >= 0 & index < Length) return true; -return false; -} -} -// Продемонстрировать применение отказоустойчивого массива. -class FSDemo { -static void Main() { -FailSoftArray fs = new FailSoftArray(5); -// Поместить ряд значений в массив fs. -for(int i=0; i < fs.Length; i++) -fs[i] = i; -// А теперь воспользоваться индексами -// типа int и double для обращения к массиву. -Console.WriteLine("fs[1] : " + fs[1]); -Console.WriteLine("fs[2] : " + fs[2]); -Console.WriteLine("fs[1.1]: " + fs[l.l]); -}Console.WriteLine("fs[1.6]: " + fs[1.6]); -} \ No newline at end of file diff --git a/El'GarnanPluginSystem/codetwo (2).txt b/El'GarnanPluginSystem/codetwo (2).txt deleted file mode 100644 index 761a3427..00000000 --- a/El'GarnanPluginSystem/codetwo (2).txt +++ /dev/null @@ -1,1558 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -using Dalamud.Configuration; -using Dalamud.Configuration.Internal; -using Dalamud.Data; -using Dalamud.Game.Gui; -using Dalamud.Game.Text; -using Dalamud.Game.Text.Sanitizer; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows.PluginInstaller; -using Dalamud.Interface.Internal.Windows.Settings; -using Dalamud.Plugin.Internal; -using Dalamud.Plugin.Internal.AutoUpdate; -using Dalamud.Plugin.Internal.Types; -using Dalamud.Plugin.Internal.Types.Manifest; -using Dalamud.Plugin.Ipc; -using Dalamud.Plugin.Ipc.Exceptions; -using Dalamud.Plugin.Ipc.Internal; - -using Serilog; - -namespace Dalamud.Plugin; - -/// -/// This class acts as an interface to various objects needed to interact with Dalamud and the game. -/// -internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposable -{ - private readonly LocalPlugin plugin; - private readonly PluginConfigurations configs; - private readonly UiBuilder uiBuilder; - - /// - /// Initializes a new instance of the class. - /// Set up the interface and populate all fields needed. - /// - /// The plugin this interface belongs to. - /// The reason the plugin was loaded. - internal DalamudPluginInterface( - LocalPlugin plugin, - PluginLoadReason reason) - { - this.plugin = plugin; - var configuration = Service.Get(); - var dataManager = Service.Get(); - var localization = Service.Get(); - - this.UiBuilder = this.uiBuilder = new(plugin, plugin.Name); - - this.configs = Service.Get().PluginConfigs; - this.Reason = reason; - this.SourceRepository = this.IsDev ? SpecialPluginSource.DevPlugin : plugin.Manifest.InstalledFromUrl; - this.IsTesting = plugin.IsTesting; - - this.LoadTime = DateTime.Now; - this.LoadTimeUTC = DateTime.UtcNow; - - this.GeneralChatType = configuration.GeneralChatType; - this.Sanitizer = new Sanitizer(dataManager.Language); - if (configuration.LanguageOverride != null) - { - this.UiLanguage = configuration.LanguageOverride; - } - else - { - var currentUiLang = CultureInfo.CurrentUICulture; - if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) - this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; - else - this.UiLanguage = "en"; - } - - localization.LocalizationChanged += this.OnLocalizationChanged; - configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; - } - - /// - /// Event that gets fired when loc is changed - /// - public event IDalamudPluginInterface.LanguageChangedDelegate? LanguageChanged; - - /// - /// Event that is fired when the active list of plugins is changed. - /// - public event IDalamudPluginInterface.ActivePluginsChangedDelegate? ActivePluginsChanged; - - /// - /// Gets the reason this plugin was loaded. - /// - public PluginLoadReason Reason { get; } - - /// - /// Gets a value indicating whether or not auto-updates have already completed this session. - /// - public bool IsAutoUpdateComplete => Service.Get().IsAutoUpdateComplete; - - /// - /// Gets the repository from which this plugin was installed. - /// - /// If a plugin was installed from the official/main repository, this will return the value of - /// . Developer plugins will return the value of - /// . - /// - public string SourceRepository { get; } - - /// - /// Gets the current internal plugin name. - /// - public string InternalName => this.plugin.InternalName; - - /// - /// Gets the plugin's manifest. - /// - public IPluginManifest Manifest => this.plugin.Manifest; - - /// - /// Gets a value indicating whether this is a dev plugin. - /// - public bool IsDev => this.plugin.IsDev; - - /// - /// Gets a value indicating whether this is a testing release of a plugin. - /// - /// - /// Dev plugins have undefined behavior for this value, but can be expected to return false. - /// - public bool IsTesting { get; } - - /// - /// Gets the time that this plugin was loaded. - /// - public DateTime LoadTime { get; } - - /// - /// Gets the UTC time that this plugin was loaded. - /// - public DateTime LoadTimeUTC { get; } - - /// - /// Gets the timespan delta from when this plugin was loaded. - /// - public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; - - /// - /// Gets the directory Dalamud assets are stored in. - /// - public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; - - /// - /// Gets the location of your plugin assembly. - /// - public FileInfo AssemblyLocation => this.plugin.DllFile; - - /// - /// Gets the directory your plugin configurations are stored in. - /// - public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); - - /// - /// Gets the config file of your plugin. - /// - public FileInfo ConfigFile => this.configs.GetConfigFile(this.plugin.InternalName); - - /// - /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. - /// - public IUiBuilder UiBuilder { get; private set; } - - /// - /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. - /// - public bool IsDevMenuOpen => Service.GetNullable() is { IsDevMenuOpen: true }; // Can be null during boot - - /// - /// Gets a value indicating whether a debugger is attached. - /// - public bool IsDebugging => Debugger.IsAttached; - - /// - /// Gets the current UI language in two-letter iso format. - /// - public string UiLanguage { get; private set; } - - /// - /// Gets serializer class with functions to remove special characters from strings. - /// - public ISanitizer Sanitizer { get; } - - /// - /// Gets the chat type used by default for plugin messages. - /// - public XivChatType GeneralChatType { get; private set; } - - /// - /// Gets a list of installed plugins along with their current state. - /// - public IEnumerable InstalledPlugins => - Service.Get().InstalledPlugins.Select(p => new ExposedPlugin(p)); - - /// - /// Gets the internal implementation. - /// - internal UiBuilder LocalUiBuilder => this.uiBuilder; - - /// - /// Opens the , with an optional search term. - /// - /// The page to open the installer to. Defaults to the "All Plugins" page. - /// An optional search text to input in the search box. - /// Returns false if the DalamudInterface was null. - public bool OpenPluginInstallerTo(PluginInstallerOpenKind openTo = PluginInstallerOpenKind.AllPlugins, string? searchText = null) - { - var dalamudInterface = Service.GetNullable(); // Can be null during boot - if (dalamudInterface == null) - { - return false; - } - - dalamudInterface.OpenPluginInstallerTo(openTo); - dalamudInterface.SetPluginInstallerSearchText(searchText ?? string.Empty); - - return true; - } - - /// - /// Opens the , with an optional search term. - /// - /// The tab to open the settings to. Defaults to the "General" tab. - /// An optional search text to input in the search box. - /// Returns false if the DalamudInterface was null. - public bool OpenDalamudSettingsTo(SettingsOpenKind openTo = SettingsOpenKind.General, string? searchText = null) - { - var dalamudInterface = Service.GetNullable(); // Can be null during boot - if (dalamudInterface == null) - { - return false; - } - - dalamudInterface.OpenSettingsTo(openTo); - dalamudInterface.SetSettingsSearchText(searchText ?? string.Empty); - - return true; - } - - /// - /// Opens the dev menu bar. - /// - /// Returns false if the DalamudInterface was null. - public bool OpenDeveloperMenu() - { - var dalamudInterface = Service.GetNullable(); // Can be null during boot - if (dalamudInterface == null) - { - return false; - } - - dalamudInterface.OpenDevMenu(); - return true; - } - - #region IPC - - /// - public T GetOrCreateData(string tag, Func dataGenerator) where T : class - => Service.Get().GetOrCreateData(tag, dataGenerator); - - /// - public void RelinquishData(string tag) - => Service.Get().RelinquishData(tag); - - /// - public bool TryGetData(string tag, [NotNullWhen(true)] out T? data) where T : class - => Service.Get().TryGetData(tag, out data); - - /// - public T? GetData(string tag) where T : class - => Service.Get().GetData(tag); - - /// - /// Gets an IPC provider. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC provider. - /// This is thrown when the requested types do not match the previously registered types are different. - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); - - /// - /// Gets an IPC subscriber. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC subscriber. - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); - - #endregion - - #region Configuration - - /// - /// Save a plugin configuration(inheriting IPluginConfiguration). - /// - /// The current configuration. - public void SavePluginConfig(IPluginConfiguration? currentConfig) - { - if (currentConfig == null) - return; - - this.configs.Save(currentConfig, this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId); - } - - /// - /// Get a previously saved plugin configuration or null if none was saved before. - /// - /// A previously saved config or null if none was saved before. - public IPluginConfiguration? GetPluginConfig() - { - // This is done to support json deserialization of plugin configurations - // even after running an in-game update of plugins, where the assembly version - // changes. - // Eventually it might make sense to have a separate method on this class - // T GetPluginConfig() where T : IPluginConfiguration - // that can invoke LoadForType() directly instead of via reflection - // This is here for now to support the current plugin API - foreach (var type in Assembly.GetCallingAssembly().GetTypes()) - { - if (type.IsAssignableTo(typeof(IPluginConfiguration))) - { - var mi = this.configs.GetType().GetMethod("LoadForType"); - var fn = mi.MakeGenericMethod(type); - return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.plugin.InternalName }); - } - } - - // this shouldn't be a thing, I think, but just in case - return this.configs.Load(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId); - } - - /// - /// Get the config directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. - public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.plugin.InternalName); - - /// - /// Get the loc directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. - public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.plugin.InternalName, "loc")); - - #endregion - - #region Chat Links - - // TODO API9: Move to chatgui, don't allow passing own commandId - - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) - { - return Service.Get().AddChatLinkHandler(this.plugin.InternalName, commandId, commandAction); - } - - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - public void RemoveChatLinkHandler(uint commandId) - { - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName, commandId); - } - - /// - /// Removes all chat link handlers registered by the plugin. - /// - public void RemoveChatLinkHandler() - { - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName); - } - #endregion - - #region Dependency Injection - - /// - public T? Create(params object[] scopedObjects) where T : class - { - var t = this.CreateAsync(scopedObjects); - t.Wait(); - - if (t.Exception is { } e) - { - Log.Error( - e, - "{who}: Exception during {where}: {what}", - this.plugin.Name, - nameof(this.Create), - typeof(T).FullName ?? typeof(T).Name); - } - - return t.IsCompletedSuccessfully ? t.Result : null; - } - - /// - public async Task CreateAsync(params object[] scopedObjects) where T : class => - (T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects)); - - /// - public bool Inject(object instance, params object[] scopedObjects) - { - var t = this.InjectAsync(instance, scopedObjects); - t.Wait(); - - if (t.Exception is { } e) - { - Log.Error( - e, - "{who}: Exception during {where}: {what}", - this.plugin.Name, - nameof(this.Inject), - instance.GetType().FullName ?? instance.GetType().Name); - } - - return t.IsCompletedSuccessfully; - } - - /// - public Task InjectAsync(object instance, params object[] scopedObjects) => - this.plugin.ServiceScope!.InjectPropertiesAsync(instance, this.GetPublicIocScopes(scopedObjects)); - - #endregion - - /// Unregister the plugin and dispose all references. - /// Dalamud internal use only. - public void Dispose() - { - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName); - Service.Get().LocalizationChanged -= this.OnLocalizationChanged; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; - this.uiBuilder.DisposeInternal(); - } - - /// - /// Dispatch the active plugins changed event. - /// - /// What action caused this event to be fired. - /// If this plugin was affected by the change. - internal void NotifyActivePluginsChanged(PluginListInvalidationKind kind, bool affectedThisPlugin) - { - if (this.ActivePluginsChanged is { } callback) - { - foreach (var action in callback.GetInvocationList().Cast()) - { - try - { - action(kind, affectedThisPlugin); - } - catch (Exception ex) - { - Log.Error(ex, "Exception during raise of {handler}", action.Method); - } - } - } - } - - private void OnLocalizationChanged(string langCode) - { - this.UiLanguage = langCode; - - if (this.LanguageChanged is { } callback) - { - foreach (var action in callback.GetInvocationList().Cast()) - { - try - { - action(langCode); - } - catch (Exception ex) - { - Log.Error(ex, "Exception during raise of {handler}", action.Method); - } - } - } - } - - private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) - { - this.GeneralChatType = dalamudConfiguration.GeneralChatType; - } - - private object[] GetPublicIocScopes(IEnumerable scopedObjects) - { - return scopedObjects.Append(this).ToArray(); - } -} -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Threading.Tasks; - -using Dalamud.Configuration; -using Dalamud.Game.Text; -using Dalamud.Game.Text.Sanitizer; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface; -using Dalamud.Interface.Internal.Windows.PluginInstaller; -using Dalamud.Interface.Internal.Windows.Settings; -using Dalamud.Plugin.Internal.Types.Manifest; -using Dalamud.Plugin.Ipc; -using Dalamud.Plugin.Ipc.Exceptions; -using Dalamud.Plugin.Ipc.Internal; - -namespace Dalamud.Plugin; - -/// -/// This interface acts as an interface to various objects needed to interact with Dalamud and the game. -/// -public interface IDalamudPluginInterface -{ - /// - /// Delegate for localization change with two-letter iso lang code. - /// - /// The new language code. - public delegate void LanguageChangedDelegate(string langCode); - - /// - /// Delegate for events that listen to changes to the list of active plugins. - /// - /// What action caused this event to be fired. - /// If this plugin was affected by the change. - public delegate void ActivePluginsChangedDelegate(PluginListInvalidationKind kind, bool affectedThisPlugin); - - /// - /// Event that gets fired when loc is changed - /// - event LanguageChangedDelegate LanguageChanged; - - /// - /// Event that is fired when the active list of plugins is changed. - /// - event ActivePluginsChangedDelegate ActivePluginsChanged; - - /// - /// Gets the reason this plugin was loaded. - /// - PluginLoadReason Reason { get; } - - /// - /// Gets a value indicating whether or not auto-updates have already completed this session. - /// - bool IsAutoUpdateComplete { get; } - - /// - /// Gets the repository from which this plugin was installed. - /// - /// If a plugin was installed from the official/main repository, this will return the value of - /// . Developer plugins will return the value of - /// . - /// - string SourceRepository { get; } - - /// - /// Gets the current internal plugin name. - /// - string InternalName { get; } - - /// - /// Gets the plugin's manifest. - /// - IPluginManifest Manifest { get; } - - /// - /// Gets a value indicating whether this is a dev plugin. - /// - bool IsDev { get; } - - /// - /// Gets a value indicating whether this is a testing release of a plugin. - /// - /// - /// Dev plugins have undefined behavior for this value, but can be expected to return false. - /// - bool IsTesting { get; } - - /// - /// Gets the time that this plugin was loaded. - /// - DateTime LoadTime { get; } - - /// - /// Gets the UTC time that this plugin was loaded. - /// - DateTime LoadTimeUTC { get; } - - /// - /// Gets the timespan delta from when this plugin was loaded. - /// - TimeSpan LoadTimeDelta { get; } - - /// - /// Gets the directory Dalamud assets are stored in. - /// - DirectoryInfo DalamudAssetDirectory { get; } - - /// - /// Gets the location of your plugin assembly. - /// - FileInfo AssemblyLocation { get; } - - /// - /// Gets the directory your plugin configurations are stored in. - /// - DirectoryInfo ConfigDirectory { get; } - - /// - /// Gets the config file of your plugin. - /// - FileInfo ConfigFile { get; } - - /// - /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. - /// - IUiBuilder UiBuilder { get; } - - /// - /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. - /// - bool IsDevMenuOpen { get; } - - /// - /// Gets a value indicating whether a debugger is attached. - /// - bool IsDebugging { get; } - - /// - /// Gets the current UI language in two-letter iso format. - /// - string UiLanguage { get; } - - /// - /// Gets serializer class with functions to remove special characters from strings. - /// - ISanitizer Sanitizer { get; } - - /// - /// Gets the chat type used by default for plugin messages. - /// - XivChatType GeneralChatType { get; } - - /// - /// Gets a list of installed plugins along with their current state. - /// - IEnumerable InstalledPlugins { get; } - - /// - /// Opens the , with an optional search term. - /// - /// The page to open the installer to. Defaults to the "All Plugins" page. - /// An optional search text to input in the search box. - /// Returns false if the DalamudInterface was null. - bool OpenPluginInstallerTo(PluginInstallerOpenKind openTo = PluginInstallerOpenKind.AllPlugins, string? searchText = null); - - /// - /// Opens the , with an optional search term. - /// - /// The tab to open the settings to. Defaults to the "General" tab. - /// An optional search text to input in the search box. - /// Returns false if the DalamudInterface was null. - bool OpenDalamudSettingsTo(SettingsOpenKind openTo = SettingsOpenKind.General, string? searchText = null); - - /// - /// Opens the dev menu bar. - /// - /// Returns false if the DalamudInterface was null. - bool OpenDeveloperMenu(); - - /// - T GetOrCreateData(string tag, Func dataGenerator) where T : class; - - /// - void RelinquishData(string tag); - - /// - bool TryGetData(string tag, [NotNullWhen(true)] out T? data) where T : class; - - /// - T? GetData(string tag) where T : class; - - /// - /// Gets an IPC provider. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC provider. - /// This is thrown when the requested types do not match the previously registered types are different. - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - ICallGateProvider GetIpcProvider(string name); - - /// - /// Gets an IPC subscriber. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC subscriber. - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - ICallGateSubscriber GetIpcSubscriber(string name); - - /// - /// Save a plugin configuration(inheriting IPluginConfiguration). - /// - /// The current configuration. - void SavePluginConfig(IPluginConfiguration? currentConfig); - - /// - /// Get a previously saved plugin configuration or null if none was saved before. - /// - /// A previously saved config or null if none was saved before. - IPluginConfiguration? GetPluginConfig(); - - /// - /// Get the config directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. - string GetPluginConfigDirectory(); - - /// - /// Get the loc directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. - string GetPluginLocDirectory(); - - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction); - - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - void RemoveChatLinkHandler(uint commandId); - - /// - /// Removes all chat link handlers registered by the plugin. - /// - void RemoveChatLinkHandler(); - - /// - /// Create a new object of the provided type using its default constructor, then inject objects and properties. - /// - /// Objects to inject additionally. - /// The type to create. - /// The created and initialized type, or null on failure. - T? Create(params object[] scopedObjects) where T : class; - - /// - /// Create a new object of the provided type using its default constructor, then inject objects and properties. - /// - /// Objects to inject additionally. - /// The type to create. - /// A task representing the created and initialized type. - Task CreateAsync(params object[] scopedObjects) where T : class; - - /// - /// Inject services into properties on the provided object instance. - /// - /// The instance to inject services into. - /// Objects to inject additionally. - /// Whether the injection succeeded. - bool Inject(object instance, params object[] scopedObjects); - - /// - /// Inject services into properties on the provided object instance. - /// - /// The instance to inject services into. - /// Objects to inject additionally. - /// A representing the status of the operation. - Task InjectAsync(object instance, params object[] scopedObjects); -}using Dalamud.Plugin.Internal.Types; - -namespace Dalamud.Plugin; - -/// -/// Interface representing an installed plugin, to be exposed to other plugins. -/// -public interface IExposedPlugin -{ - /// - /// Gets the name of the plugin. - /// - string Name { get; } - - /// - /// Gets the internal name of the plugin. - /// - string InternalName { get; } - - /// - /// Gets a value indicating whether the plugin is loaded. - /// - bool IsLoaded { get; } - - /// - /// Gets the version of the plugin. - /// - Version Version { get; } - - /// - /// Gets a value indicating whether the plugin has a main UI. - /// - public bool HasMainUi { get; } - - /// - /// Gets a value indicating whether the plugin has a config UI. - /// - public bool HasConfigUi { get; } - - /// - /// Opens the main UI of the plugin. - /// Throws if is false. - /// - public void OpenMainUi(); - - /// - /// Opens the config UI of the plugin. - /// Throws if is false. - /// - public void OpenConfigUi(); -} - -/// -/// Internal representation of an installed plugin, to be exposed to other plugins. -/// -/// The plugin. -internal sealed class ExposedPlugin(LocalPlugin plugin) : IExposedPlugin -{ - /// - public string Name => plugin.Name; - - /// - public string InternalName => plugin.InternalName; - - /// - public bool IsLoaded => plugin.IsLoaded; - - /// - public Version Version => plugin.EffectiveVersion; - - /// - public bool HasMainUi => plugin.DalamudInterface?.LocalUiBuilder.HasMainUi ?? false; - - /// - public bool HasConfigUi => plugin.DalamudInterface?.LocalUiBuilder.HasConfigUi ?? false; - - /// - public void OpenMainUi() - { - if (plugin.DalamudInterface?.LocalUiBuilder.HasMainUi == true) - plugin.DalamudInterface.LocalUiBuilder.OpenMain(); - else - throw new InvalidOperationException("Plugin does not have a main UI."); - } - - /// - public void OpenConfigUi() - { - if (plugin.DalamudInterface?.LocalUiBuilder.HasConfigUi == true) - plugin.DalamudInterface.LocalUiBuilder.OpenConfig(); - else - throw new InvalidOperationException("Plugin does not have a config UI."); - } -}namespace Dalamud.Plugin; - -/// -/// Causes for a change to the plugin list. -/// -public enum PluginListInvalidationKind -{ - /// - /// An installer-initiated update reloaded plugins. - /// - Update, - - /// - /// An auto-update reloaded plugins. - /// - AutoUpdate, -}namespace Dalamud.Plugin; - -/// -/// This enum reflects reasons for loading a plugin. -/// -public enum PluginLoadReason -{ - /// - /// We don't know why this plugin was loaded. - /// - Unknown, - - /// - /// This plugin was loaded because it was installed with the plugin installer. - /// - Installer, - - /// - /// This plugin was loaded because it was just updated. - /// - Update, - - /// - /// This plugin was loaded because it was told to reload. - /// - Reload, - - /// - /// This plugin was loaded because the game was started or Dalamud was reinjected. - /// - Boot, -} - -// TODO(api9): This should be a mask, so that we can combine Installer | ProfileLoaded -using System.Collections.Generic; -using System.Runtime.InteropServices; - -using Dalamud.Game.Addon.Lifecycle; -using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; - -namespace Dalamud.Plugin.Services; - -/// -/// This class provides events for in-game addon lifecycles. -/// -public interface IAddonLifecycle -{ - /// - /// Delegate for receiving addon lifecycle event messages. - /// - /// The event type that triggered the message. - /// Information about what addon triggered the message. - public delegate void AddonEventDelegate(AddonEvent type, AddonArgs args); - - /// - /// Register a listener that will trigger on the specified event and any of the specified addons. - /// - /// Event type to trigger on. - /// Addon names that will trigger the handler to be invoked. - /// The handler to invoke. - void RegisterListener(AddonEvent eventType, IEnumerable addonNames, AddonEventDelegate handler); - - /// - /// Register a listener that will trigger on the specified event only for the specified addon. - /// - /// Event type to trigger on. - /// The addon name that will trigger the handler to be invoked. - /// The handler to invoke. - void RegisterListener(AddonEvent eventType, string addonName, AddonEventDelegate handler); - - /// - /// Register a listener that will trigger on the specified event for any addon. - /// - /// Event type to trigger on. - /// The handler to invoke. - void RegisterListener(AddonEvent eventType, AddonEventDelegate handler); - - /// - /// Unregister listener from specified event type and specified addon names. - /// - /// - /// If a specific handler is not provided, all handlers for the event type and addon names will be unregistered. - /// - /// Event type to deregister. - /// Addon names to deregister. - /// Optional specific handler to remove. - void UnregisterListener(AddonEvent eventType, IEnumerable addonNames, [Optional] AddonEventDelegate handler); - - /// - /// Unregister all listeners for the specified event type and addon name. - /// - /// - /// If a specific handler is not provided, all handlers for the event type and addons will be unregistered. - /// - /// Event type to deregister. - /// Addon name to deregister. - /// Optional specific handler to remove. - void UnregisterListener(AddonEvent eventType, string addonName, [Optional] AddonEventDelegate handler); - - /// - /// Unregister an event type handler.
This will only remove a handler that is added via . - ///
- /// - /// If a specific handler is not provided, all handlers for the event type and addons will be unregistered. - /// - /// Event type to deregister. - /// Optional specific handler to remove. - void UnregisterListener(AddonEvent eventType, [Optional] AddonEventDelegate handler); - - /// - /// Unregister all events that use the specified handlers. - /// - /// Handlers to remove. - void UnregisterListener(params AddonEventDelegate[] handlers); -}using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -using Dalamud.Common; -using Dalamud.Configuration.Internal; -using Dalamud.Game; -using Dalamud.Plugin.Internal; -using Dalamud.Storage; -using Dalamud.Utility; -using Dalamud.Utility.Timing; -using PInvoke; -using Serilog; - -#if DEBUG -[assembly: InternalsVisibleTo("Dalamud.CorePlugin")] -#endif - -[assembly: InternalsVisibleTo("Dalamud.Test")] -[assembly: InternalsVisibleTo("Dalamud.DevHelpers")] - -namespace Dalamud; - -/// -/// The main Dalamud class containing all subsystems. -/// -[ServiceManager.ProvidedService] -internal sealed class Dalamud : IServiceType -{ - #region Internals - - private static int shownServiceError = 0; - private readonly ManualResetEvent unloadSignal; - - #endregion - - /// - /// Initializes a new instance of the class. - /// - /// DalamudStartInfo instance. - /// ReliableFileStorage instance. - /// The Dalamud configuration. - /// Event used to signal the main thread to continue. - public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) - { - this.StartInfo = info; - - this.unloadSignal = new ManualResetEvent(false); - this.unloadSignal.Reset(); - - // Directory resolved signatures(CS, our own) will be cached in - var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs")); - if (!cacheDir.Exists) - cacheDir.Create(); - - // Set up the SigScanner for our target module - TargetSigScanner scanner; - using (Timings.Start("SigScanner Init")) - { - scanner = new TargetSigScanner( - true, new FileInfo(Path.Combine(cacheDir.FullName, $"{this.StartInfo.GameVersion}.json"))); - } - - ServiceManager.InitializeProvidedServices( - this, - fs, - configuration, - scanner, - Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride)); - - // Set up FFXIVClientStructs - this.SetupClientStructsResolver(cacheDir); - - void KickoffGameThread() - { - Log.Verbose("=============== GAME THREAD KICKOFF ==============="); - Timings.Event("Game thread kickoff"); - NativeFunctions.SetEvent(mainThreadContinueEvent); - } - - void HandleServiceInitFailure(Task t) - { - Log.Error(t.Exception!, "Service initialization failure"); - - if (Interlocked.CompareExchange(ref shownServiceError, 1, 0) != 0) - return; - - Util.Fatal( - "Dalamud failed to load all necessary services.\n\nThe game will continue, but you may not be able to use plugins.", - "Dalamud", false); - } - - ServiceManager.InitializeEarlyLoadableServices() - .ContinueWith( - t => - { - if (t.IsCompletedSuccessfully) - return; - - HandleServiceInitFailure(t); - }); - - ServiceManager.BlockingResolved.ContinueWith( - t => - { - if (t.IsCompletedSuccessfully) - { - KickoffGameThread(); - return; - } - - HandleServiceInitFailure(t); - }); - - this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero); - NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter); - Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}"); - - var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF"; - this.DebugExceptionFilter = Service.Get().ScanText(debugSig); - Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}"); - } - - /// - /// Gets the start information for this Dalamud instance. - /// - internal DalamudStartInfo StartInfo { get; private set; } - - /// - /// Gets location of stored assets. - /// - internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory!); - - /// - /// Gets the in-game default exception filter. - /// - private nint DefaultExceptionFilter { get; } - - /// - /// Gets the in-game debug exception filter. - /// - private nint DebugExceptionFilter { get; } - - /// - /// Signal to the crash handler process that we should restart the game. - /// - public static void RestartGame() - { - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments); - - RaiseException(0x12345678, 0, 0, IntPtr.Zero); - Process.GetCurrentProcess().Kill(); - } - - /// - /// Queue an unload of Dalamud when it gets the chance. - /// - public void Unload() - { - Log.Information("Trigger unload"); - - var reportCrashesSetting = Service.GetNullable()?.ReportShutdownCrashes ?? true; - var pmHasDevPlugins = Service.GetNullable()?.InstalledPlugins.Any(x => x.IsDev) ?? false; - if (!reportCrashesSetting && !pmHasDevPlugins) - { - // Leaking on purpose for now - var attribs = Kernel32.SECURITY_ATTRIBUTES.Create(); - Kernel32.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE"); - } - - this.unloadSignal.Set(); - } - - /// - /// Wait for an unload request to start. - /// - public void WaitForUnload() - { - this.unloadSignal.WaitOne(); - } - - /// - /// Replace the current exception handler with the default one. - /// - internal void UseDefaultExceptionHandler() => - this.SetExceptionHandler(this.DefaultExceptionFilter); - - /// - /// Replace the current exception handler with a debug one. - /// - internal void UseDebugExceptionHandler() => - this.SetExceptionHandler(this.DebugExceptionFilter); - - /// - /// Disable the current exception handler. - /// - internal void UseNoExceptionHandler() => - this.SetExceptionHandler(nint.Zero); - - /// - /// Helper function to set the exception handler. - /// - private void SetExceptionHandler(nint newFilter) - { - var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter); - Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter); - } - - private void SetupClientStructsResolver(DirectoryInfo cacheDir) - { - using (Timings.Start("CS Resolver Init")) - { - // the resolver tracks version as a field in the json - InteropGenerator.Runtime.Resolver.GetInstance.Setup(Service.Get().SearchBase, $"{this.StartInfo.GameVersion}", new FileInfo(Path.Combine(cacheDir.FullName, "cs.json"))); - FFXIVClientStructs.Interop.Generated.Addresses.Register(); - InteropGenerator.Runtime.Resolver.GetInstance.Resolve(); - } - } -} - -namespace WpfPluginSample.PluginFramework -{ - public interface IPluginController - { - void Initialize(); - - object CreateMainView(); - - void Shutdown(); - } -} - -using System; -using System.Collections.Generic; -using System.Runtime.Remoting; -using System.Runtime.Remoting.Channels; -using System.Runtime.Remoting.Channels.Ipc; -using System.Runtime.Serialization.Formatters; - -namespace WpfPluginSample.PluginFramework -{ - public static class RemoteServiceLocator - { - private static readonly object initializeIpcChannelLock = new object(); - - - public static TInterface GetService() - where TInterface : class - { - string objectUri = "ipc://" + GetIpcPortName() + "/" + typeof(TInterface).FullName; - return (TInterface)Activator.GetObject(typeof(TInterface), objectUri); - } - - public static string GetIpcPortName() - where TInterface : class - { - // TODO: Optional use the GUID attribute or introduce an own assembly attribute. - return typeof(TInterface).Assembly.GetName().Name; - } - - public static void RegisterType() - where TInterface : class - where TImplementation : MarshalByRefObject, TInterface - { - RemotingConfiguration.RegisterWellKnownServiceType(typeof(TImplementation), typeof(TInterface).FullName, WellKnownObjectMode.Singleton); - } - - public static void RegisterInstance(MarshalByRefObject instance) - where TInterface : class - { - RemotingServices.Marshal(instance, typeof(TInterface).FullName); - } - - // Call this only once for a process - // ipcPortName is necessary if process provides services - public static IpcChannel InitializeIpcChannel(string ipcPortName = null) - { - lock (initializeIpcChannelLock) - { - IpcChannel channel; - if (string.IsNullOrEmpty(ipcPortName)) - { - // Only a client channel - channel = new IpcChannel(); - } - else - { - // Channel that supports server and client usage - var serverProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = TypeFilterLevel.Full }; - var clientProvider = new BinaryClientFormatterSinkProvider(); - var properties = new Dictionary(); - properties["portName"] = ipcPortName; - channel = new IpcChannel(properties, clientProvider, serverProvider); - } - - ChannelServices.RegisterChannel(channel, false); - return channel; - } - } - } -} -using System.AddIn.Contract; -using System.Runtime.Remoting.Messaging; - -namespace WpfPluginSample.PluginFramework.Internals -{ - public interface IPluginLoader - { - INativeHandleContract LoadPlugin(string assembly, string typeName); - - [OneWay] - void Shutdown(); - } -} - -using System; -using System.AddIn.Contract; - -namespace WpfPluginSample.PluginFramework.Internals -{ - public class NativeHandleContractInsulator : RemoteService, INativeHandleContract - { - private readonly INativeHandleContract source; - - public NativeHandleContractInsulator(INativeHandleContract source) - { - this.source = source; - } - - public IntPtr GetHandle() - { - return source.GetHandle(); - } - - public int AcquireLifetimeToken() - { - return source.AcquireLifetimeToken(); - } - - public int GetRemoteHashCode() - { - return source.GetRemoteHashCode(); - } - - public IContract QueryContract(string contractIdentifier) - { - return source.QueryContract(contractIdentifier); - } - - public bool RemoteEquals(IContract contract) - { - return source.RemoteEquals(contract); - } - - public string RemoteToString() - { - return source.RemoteToString(); - } - - public void RevokeLifetimeToken(int token) - { - source.RevokeLifetimeToken(token); - } - } -} - -using System; -using System.Security; - -namespace WpfPluginSample.PluginFramework.Internals -{ - /// - /// Base class for singleton services which can be accessed over application domain boundaries via remoting. - /// - [Serializable] - public abstract class RemoteService : MarshalByRefObject - { - [SecurityCritical] - public override object InitializeLifetimeService() - { - return null; - } - } -} - - -using System; - -namespace WpfPluginSample.PluginFramework.Internals -{ - public class TypedArgs - { - public TypedArgs(int parentProcessId, string assemblyFile, string instanceName) - { - ParentProcessId = parentProcessId; - AssemblyFile = assemblyFile; - InstanceName = instanceName; - } - - - public int ParentProcessId { get; } - - public string AssemblyFile { get; } - - public string InstanceName { get; } - - - public static TypedArgs FromArgs(string[] args) - { - int parentProcessId = Convert.ToInt32(args[0]); - var assemblyFile = args[1]; - var name = args[2]; - return new TypedArgs(parentProcessId, assemblyFile, name); - } - - public string ToArgs() - { - // Enclose the AssemblyFile within " chars because the path might contain spaces. - return ParentProcessId + " \"" + AssemblyFile + "\" " + InstanceName; - } - } -} diff --git a/El'GarnanPluginSystem/imguicodeexample.txt b/El'GarnanPluginSystem/imguicodeexample.txt deleted file mode 100644 index abceb1f1..00000000 --- a/El'GarnanPluginSystem/imguicodeexample.txt +++ /dev/null @@ -1,1450 +0,0 @@ -// Dear ImGui: standalone example application for DirectX 12 - -// Learn about Dear ImGui: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). -// - Introduction, links and more at the top of imgui.cpp - -// Important: to compile on 32-bit systems, the DirectX12 backend requires code to be compiled with '#define ImTextureID ImU64'. -// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. -// This define is set in the example .vcxproj file and need to be replicated in your app or by adding it to your imconfig.h file. - -#include "imgui.h" -#include "imgui_impl_win32.h" -#include "imgui_impl_dx12.h" -#include -#include - -#include - -#include "IconsFontAwesome6.h" - -// For more info creating and using the compressed font follow: -// TTF TO COMPRESSED: https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp -// USE COMPRESSED FONT: https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#loading-font-data-embedded-in-source-code -#include "fa-solid-900.h" - -#include "ImGuiNotify.hpp" - -#ifdef _DEBUG -#define DX12_ENABLE_DEBUG_LAYER -#endif - -#ifdef DX12_ENABLE_DEBUG_LAYER -#include -#pragma comment(lib, "dxguid.lib") -#endif - -#include "imgui_internal.h" - -struct FrameContext -{ - ID3D12CommandAllocator* CommandAllocator; - UINT64 FenceValue; -}; - -// Data -static int const NUM_FRAMES_IN_FLIGHT = 3; -static FrameContext g_frameContext[NUM_FRAMES_IN_FLIGHT] = {}; -static UINT g_frameIndex = 0; - -static int const NUM_BACK_BUFFERS = 3; -static ID3D12Device* g_pd3dDevice = nullptr; -static ID3D12DescriptorHeap* g_pd3dRtvDescHeap = nullptr; -static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = nullptr; -static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr; -static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr; -static ID3D12Fence* g_fence = nullptr; -static HANDLE g_fenceEvent = nullptr; -static UINT64 g_fenceLastSignaledValue = 0; -static IDXGISwapChain3* g_pSwapChain = nullptr; -static bool g_SwapChainOccluded = false; -static HANDLE g_hSwapChainWaitableObject = nullptr; -static ID3D12Resource* g_mainRenderTargetResource[NUM_BACK_BUFFERS] = {}; -static D3D12_CPU_DESCRIPTOR_HANDLE g_mainRenderTargetDescriptor[NUM_BACK_BUFFERS] = {}; - -// Forward declarations of helper functions -bool CreateDeviceD3D(HWND hWnd); -void CleanupDeviceD3D(); -void CreateRenderTarget(); -void CleanupRenderTarget(); -void WaitForLastSubmittedFrame(); -FrameContext* WaitForNextFrameResources(); -LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// ImGui Theme by janekb04(https://github.com/janekb04), Find more themes at https://github.com/ocornut/imgui/issues/707 -void EmbraceTheDarkness() -{ - ImVec4* colors = ImGui::GetStyle().Colors; - colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 0.92f); - colors[ImGuiCol_Border] = ImVec4(0.19f, 0.19f, 0.19f, 0.29f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.24f); - colors[ImGuiCol_FrameBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); - colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.54f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f); - colors[ImGuiCol_CheckMark] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f); - colors[ImGuiCol_Button] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.00f, 0.00f, 0.36f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.20f, 0.22f, 0.23f, 0.33f); - colors[ImGuiCol_Separator] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f); - colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); - colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 0.36f); - colors[ImGuiCol_TabUnfocused] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); - colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_TableHeaderBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); - colors[ImGuiCol_TableBorderStrong] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); - colors[ImGuiCol_TableBorderLight] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f); - colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); - colors[ImGuiCol_DragDropTarget] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f); - colors[ImGuiCol_NavHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.35f); - - ImGuiStyle& style = ImGui::GetStyle(); - style.WindowPadding = ImVec2(8.00f, 8.00f); - style.FramePadding = ImVec2(5.00f, 2.00f); - style.CellPadding = ImVec2(6.00f, 6.00f); - style.ItemSpacing = ImVec2(6.00f, 6.00f); - style.ItemInnerSpacing = ImVec2(6.00f, 6.00f); - style.TouchExtraPadding = ImVec2(0.00f, 0.00f); - style.IndentSpacing = 25; - style.ScrollbarSize = 15; - style.GrabMinSize = 10; - style.WindowBorderSize = 1; - style.ChildBorderSize = 1; - style.PopupBorderSize = 1; - style.FrameBorderSize = 1; - style.TabBorderSize = 1; - style.WindowRounding = 7; - style.ChildRounding = 4; - style.FrameRounding = 3; - style.PopupRounding = 4; - style.ScrollbarRounding = 9; - style.GrabRounding = 3; - style.LogSliderDeadzone = 4; - style.TabRounding = 4; -} - -// Main code -int main(int, char**) -{ - // Create application window - //ImGui_ImplWin32_EnableDpiAwareness(); - WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; - ::RegisterClassExW(&wc); - HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); - - // Initialize Direct3D - if (!CreateDeviceD3D(hwnd)) - { - CleanupDeviceD3D(); - ::UnregisterClassW(wc.lpszClassName, wc.hInstance); - return 1; - } - - // Show the window - ::ShowWindow(hwnd, SW_SHOWDEFAULT); - ::UpdateWindow(hwnd); - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows - //io.ConfigViewportsNoAutoMerge = true; - //io.ConfigViewportsNoTaskBarIcon = true; - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - //ImGui::StyleColorsLight(); - - // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. - ImGuiStyle& style = ImGui::GetStyle(); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - style.WindowRounding = 0.0f; - style.Colors[ImGuiCol_WindowBg].w = 1.0f; - } - - // Theme setup - EmbraceTheDarkness(); - - - // Setup Platform/Renderer backends - ImGui_ImplWin32_Init(hwnd); - ImGui_ImplDX12_Init(g_pd3dDevice, NUM_FRAMES_IN_FLIGHT, - DXGI_FORMAT_R8G8B8A8_UNORM, g_pd3dSrvDescHeap, - g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), - g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); - - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. - // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. - // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. - // - Read 'docs/FONTS.md' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); - //IM_ASSERT(font != nullptr); - - // Our state - bool show_demo_window = true; - ImVec4 clear_color = ImVec4(0.025f, 0.025f, 0.025f, 1.00f); - - - - - - /** - * FontAwesome setup START (required for icons) - */ - - io.Fonts->AddFontDefault(); - - float baseFontSize = 16.0f; - float iconFontSize = baseFontSize * 2.0f / 3.0f; // FontAwesome fonts need to have their sizes reduced by 2.0f/3.0f in order to align correctly - - static constexpr ImWchar iconsRanges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0}; - ImFontConfig iconsConfig; - iconsConfig.MergeMode = true; - iconsConfig.PixelSnapH = true; - iconsConfig.GlyphMinAdvanceX = iconFontSize; - io.Fonts->AddFontFromMemoryCompressedTTF(fa_solid_900_compressed_data, fa_solid_900_compressed_size, iconFontSize, &iconsConfig, iconsRanges); - - /** - * FontAwesome setup END - */ - - - - - - - - - // Main loop - bool done = false; - while (!done) - { - // Poll and handle messages (inputs, window resize, etc.) - // See the WndProc() function below for our to dispatch events to the Win32 backend. - MSG msg; - while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) - { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - if (msg.message == WM_QUIT) - done = true; - } - if (done) - break; - - // Handle window screen locked - if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) - { - ::Sleep(10); - continue; - } - g_SwapChainOccluded = false; - - // Start the Dear ImGui frame - ImGui_ImplDX12_NewFrame(); - ImGui_ImplWin32_NewFrame(); - ImGui::NewFrame(); - - - - - - - - - - - - - - - - - - - /** - * ImGui Notify Demo Start - */ - - ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x / 2, ImGui::GetIO().DisplaySize.y / 2), ImGuiCond_Once); - ImGui::SetNextWindowSize({550, 550}, ImGuiCond_Once); - ImGui::Begin("ImGui Notify Test Window"); - - if (ImGui::CollapsingHeader("Examples without title", ImGuiTreeNodeFlags_DefaultOpen)) - { - if (ImGui::Button("Success")) - { - ImGui::InsertNotification({ImGuiToastType::Success, 3000, "That is a success! %s", "(Format here)"}); - } - - ImGui::SameLine(); - if (ImGui::Button("Warning")) - { - ImGui::InsertNotification({ImGuiToastType::Warning, 3000, "This is a warning!"}); - } - - ImGui::SameLine(); - if (ImGui::Button("Error")) - { - ImGui::InsertNotification({ImGuiToastType::Error, 3000, "Segmentation fault"}); - } - - ImGui::SameLine(); - if (ImGui::Button("Info")) - { - ImGui::InsertNotification({ImGuiToastType::Info, 3000, "Info about ImGui..."}); - } - - ImGui::SameLine(); - if (ImGui::Button("Info long")) - { - ImGui::InsertNotification({ImGuiToastType::Info, 3000, "Hi, I'm a long notification. I'm here to show you that you can write a lot of text in me. I'm also here to show you that I can wrap text, so you don't have to worry about that."}); - } - - ImGui::SameLine(); - if (ImGui::Button("Notify with button")) - { - ImGui::InsertNotification({ImGuiToastType::Error, 3000, "Click me!", [](){ImGui::InsertNotification({ImGuiToastType::Success, 3000, "Thanks for clicking!"});}, "Notification content"}); - } - } - - if (ImGui::CollapsingHeader("Do it yourself", ImGuiTreeNodeFlags_DefaultOpen)) - { - static char title[4096] = "Hello there!"; - ImGui::InputTextMultiline("Title", title, sizeof(title)); - - static char content[4096] = "General Kenobi! \n- Grevious"; - ImGui::InputTextMultiline("Content", content, sizeof(content)); - - static int duration = 5000; // 5 seconds - ImGui::InputInt("Duration (ms)", &duration, 100); - if (duration < 0) duration = 0; // Shouldn't be negative - - static const char* type_str[] = { "None", "Success", "Warning", "Error", "Info" }; - static ImGuiToastType type = ImGuiToastType::Success; - IM_ASSERT(type < ImGuiToastType::COUNT); - - if (ImGui::BeginCombo("Type", type_str[(uint8_t)type])) - { - for (auto n = 0; n < IM_ARRAYSIZE(type_str); n++) - { - const bool isSelected = ((uint8_t)type == n); - - if (ImGui::Selectable(type_str[n], isSelected)) - { - type = (ImGuiToastType)n; - } - - if (isSelected) - { - ImGui::SetItemDefaultFocus(); - } - } - - ImGui::EndCombo(); - } - - static bool enable_title = true, enable_content = true; - ImGui::Checkbox("Enable title", &enable_title); - ImGui::SameLine(); - ImGui::Checkbox("Enable content", &enable_content); - - if (ImGui::Button("Show")) - { - ImGuiToast toast(type, duration); - - if (enable_title) - toast.setTitle(title); - - if (enable_content) - toast.setContent(content); - - ImGui::InsertNotification(toast); - } - } - - ImGui::End(); - - /** - * ImGui Notify Demo End - */ - - - - - - - - - - // YOUR CODE GOES HERE - - - - - - - - - - - - /** - * Notifications Rendering Start - */ - - // Notifications style setup - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); // Disable round borders - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); // Disable borders - - // Notifications color setup - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.10f, 1.00f)); // Background color - - - // Main rendering function - ImGui::RenderNotifications(); - - - //——————————————————————————————— WARNING ——————————————————————————————— - // Argument MUST match the amount of ImGui::PushStyleVar() calls - ImGui::PopStyleVar(2); - // Argument MUST match the amount of ImGui::PushStyleColor() calls - ImGui::PopStyleColor(1); - - /** - * Notifications Rendering End - */ - - - - - - - - - - - - - - - - - - - - // Rendering - ImGui::Render(); - - FrameContext* frameCtx = WaitForNextFrameResources(); - UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); - frameCtx->CommandAllocator->Reset(); - - D3D12_RESOURCE_BARRIER barrier = {}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = g_mainRenderTargetResource[backBufferIdx]; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - g_pd3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); - g_pd3dCommandList->ResourceBarrier(1, &barrier); - - // Render Dear ImGui graphics - const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; - g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], clear_color_with_alpha, 0, nullptr); - g_pd3dCommandList->OMSetRenderTargets(1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr); - g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap); - ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList); - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; - g_pd3dCommandList->ResourceBarrier(1, &barrier); - g_pd3dCommandList->Close(); - - g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); - - // Update and Render additional Platform Windows - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(nullptr, (void*)g_pd3dCommandList); - } - - // Present - HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync - //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync - g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); - - UINT64 fenceValue = g_fenceLastSignaledValue + 1; - g_pd3dCommandQueue->Signal(g_fence, fenceValue); - g_fenceLastSignaledValue = fenceValue; - frameCtx->FenceValue = fenceValue; - } - - WaitForLastSubmittedFrame(); - - // Cleanup - ImGui_ImplDX12_Shutdown(); - ImGui_ImplWin32_Shutdown(); - ImGui::DestroyContext(); - - CleanupDeviceD3D(); - ::DestroyWindow(hwnd); - ::UnregisterClassW(wc.lpszClassName, wc.hInstance); - - return 0; -} - -// Helper functions - -bool CreateDeviceD3D(HWND hWnd) -{ - // Setup swap chain - DXGI_SWAP_CHAIN_DESC1 sd; - { - ZeroMemory(&sd, sizeof(sd)); - sd.BufferCount = NUM_BACK_BUFFERS; - sd.Width = 0; - sd.Height = 0; - sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; - sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - sd.SampleDesc.Count = 1; - sd.SampleDesc.Quality = 0; - sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - sd.Scaling = DXGI_SCALING_STRETCH; - sd.Stereo = FALSE; - } - - // [DEBUG] Enable debug interface -#ifdef DX12_ENABLE_DEBUG_LAYER - ID3D12Debug* pdx12Debug = nullptr; - if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug)))) - pdx12Debug->EnableDebugLayer(); -#endif - - // Create device - D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; - if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK) - return false; - - // [DEBUG] Setup debug interface to break on any warnings/errors -#ifdef DX12_ENABLE_DEBUG_LAYER - if (pdx12Debug != nullptr) - { - ID3D12InfoQueue* pInfoQueue = nullptr; - g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&pInfoQueue)); - pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); - pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); - pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); - pInfoQueue->Release(); - pdx12Debug->Release(); - } -#endif - - { - D3D12_DESCRIPTOR_HEAP_DESC desc = {}; - desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; - desc.NumDescriptors = NUM_BACK_BUFFERS; - desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; - desc.NodeMask = 1; - if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dRtvDescHeap)) != S_OK) - return false; - - SIZE_T rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart(); - for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) - { - g_mainRenderTargetDescriptor[i] = rtvHandle; - rtvHandle.ptr += rtvDescriptorSize; - } - } - - { - D3D12_DESCRIPTOR_HEAP_DESC desc = {}; - desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; - desc.NumDescriptors = 1; - desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK) - return false; - } - - { - D3D12_COMMAND_QUEUE_DESC desc = {}; - desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - desc.NodeMask = 1; - if (g_pd3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK) - return false; - } - - for (UINT i = 0; i < NUM_FRAMES_IN_FLIGHT; i++) - if (g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) != S_OK) - return false; - - if (g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&g_pd3dCommandList)) != S_OK || - g_pd3dCommandList->Close() != S_OK) - return false; - - if (g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence)) != S_OK) - return false; - - g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (g_fenceEvent == nullptr) - return false; - - { - IDXGIFactory4* dxgiFactory = nullptr; - IDXGISwapChain1* swapChain1 = nullptr; - if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) - return false; - if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) - return false; - if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) - return false; - swapChain1->Release(); - dxgiFactory->Release(); - g_pSwapChain->SetMaximumFrameLatency(NUM_BACK_BUFFERS); - g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); - } - - CreateRenderTarget(); - return true; -} - -void CleanupDeviceD3D() -{ - CleanupRenderTarget(); - if (g_pSwapChain) { g_pSwapChain->SetFullscreenState(false, nullptr); g_pSwapChain->Release(); g_pSwapChain = nullptr; } - if (g_hSwapChainWaitableObject != nullptr) { CloseHandle(g_hSwapChainWaitableObject); } - for (UINT i = 0; i < NUM_FRAMES_IN_FLIGHT; i++) - if (g_frameContext[i].CommandAllocator) { g_frameContext[i].CommandAllocator->Release(); g_frameContext[i].CommandAllocator = nullptr; } - if (g_pd3dCommandQueue) { g_pd3dCommandQueue->Release(); g_pd3dCommandQueue = nullptr; } - if (g_pd3dCommandList) { g_pd3dCommandList->Release(); g_pd3dCommandList = nullptr; } - if (g_pd3dRtvDescHeap) { g_pd3dRtvDescHeap->Release(); g_pd3dRtvDescHeap = nullptr; } - if (g_pd3dSrvDescHeap) { g_pd3dSrvDescHeap->Release(); g_pd3dSrvDescHeap = nullptr; } - if (g_fence) { g_fence->Release(); g_fence = nullptr; } - if (g_fenceEvent) { CloseHandle(g_fenceEvent); g_fenceEvent = nullptr; } - if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; } - -#ifdef DX12_ENABLE_DEBUG_LAYER - IDXGIDebug1* pDebug = nullptr; - if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug)))) - { - pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY); - pDebug->Release(); - } -#endif -} - -void CreateRenderTarget() -{ - for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) - { - ID3D12Resource* pBackBuffer = nullptr; - g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer)); - g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, g_mainRenderTargetDescriptor[i]); - g_mainRenderTargetResource[i] = pBackBuffer; - } -} - -void CleanupRenderTarget() -{ - WaitForLastSubmittedFrame(); - - for (UINT i = 0; i < NUM_BACK_BUFFERS; i++) - if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; } -} - -void WaitForLastSubmittedFrame() -{ - FrameContext* frameCtx = &g_frameContext[g_frameIndex % NUM_FRAMES_IN_FLIGHT]; - - UINT64 fenceValue = frameCtx->FenceValue; - if (fenceValue == 0) - return; // No fence was signaled - - frameCtx->FenceValue = 0; - if (g_fence->GetCompletedValue() >= fenceValue) - return; - - g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); - WaitForSingleObject(g_fenceEvent, INFINITE); -} - -FrameContext* WaitForNextFrameResources() -{ - UINT nextFrameIndex = g_frameIndex + 1; - g_frameIndex = nextFrameIndex; - - HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, nullptr }; - DWORD numWaitableObjects = 1; - - FrameContext* frameCtx = &g_frameContext[nextFrameIndex % NUM_FRAMES_IN_FLIGHT]; - UINT64 fenceValue = frameCtx->FenceValue; - if (fenceValue != 0) // means no fence was signaled - { - frameCtx->FenceValue = 0; - g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); - waitableObjects[1] = g_fenceEvent; - numWaitableObjects = 2; - } - - WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); - - return frameCtx; -} - -// Forward declare message handler from imgui_impl_win32.cpp -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// Win32 message handler -// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. -// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. -// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. -// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. -LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) - return true; - - switch (msg) - { - case WM_SIZE: - if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) - { - WaitForLastSubmittedFrame(); - CleanupRenderTarget(); - HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); - assert(SUCCEEDED(result) && "Failed to resize swapchain."); - CreateRenderTarget(); - } - return 0; - case WM_SYSCOMMAND: - if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu - return 0; - break; - case WM_DESTROY: - ::PostQuitMessage(0); - return 0; - } - return ::DefWindowProcW(hWnd, msg, wParam, lParam); -} -/** - * @file ImGuiNotify.hpp - * @brief A header-only library for creating toast notifications with ImGui. - * - * Based on imgui-notify by patrickcjk - * https://github.com/patrickcjk/imgui-notify - * - * @version 0.0.3 by TyomaVader - * @date 07.07.2024 - */ - -#ifndef IMGUI_NOTIFY -#define IMGUI_NOTIFY - -#pragma once - -#include // Vector for storing notifications list -#include -#include // For the notifications timed dissmiss -#include // For storing the code, which executest on the button click in the notification - -#include "imgui.h" -#include "imgui_internal.h" - -#include "IconsFontAwesome6.h" - - - - - - - - - -/** - * CONFIGURATION SECTION Start -*/ - -#define NOTIFY_MAX_MSG_LENGTH 4096 // Max message content length -#define NOTIFY_PADDING_X 20.f // Bottom-left X padding -#define NOTIFY_PADDING_Y 20.f // Bottom-left Y padding -#define NOTIFY_PADDING_MESSAGE_Y 10.f // Padding Y between each message -#define NOTIFY_FADE_IN_OUT_TIME 150 // Fade in and out duration -#define NOTIFY_DEFAULT_DISMISS 3000 // Auto dismiss after X ms (default, applied only of no data provided in constructors) -#define NOTIFY_OPACITY 0.8f // 0-1 Toast opacity -#define NOTIFY_USE_SEPARATOR false // If true, a separator will be rendered between the title and the content -#define NOTIFY_USE_DISMISS_BUTTON true // If true, a dismiss button will be rendered in the top right corner of the toast -#define NOTIFY_RENDER_LIMIT 5 // Max number of toasts rendered at the same time. Set to 0 for unlimited - -// Warning: Requires ImGui docking with multi-viewport enabled -#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW true // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window - -/** - * CONFIGURATION SECTION End -*/ - - - - - - - - -static const ImGuiWindowFlags NOTIFY_DEFAULT_TOAST_FLAGS = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing; - -#define NOTIFY_NULL_OR_EMPTY(str) (!str || !strlen(str)) -#define NOTIFY_FORMAT(fn, format, ...) if (format) {va_list args; va_start(args, format); fn(format, args, ##__VA_ARGS__); va_end(args);} - -enum class ImGuiToastType : uint8_t -{ - None, - Success, - Warning, - Error, - Info, - COUNT -}; - -enum class ImGuiToastPhase : uint8_t -{ - FadeIn, - Wait, - FadeOut, - Expired, - COUNT -}; - -enum class ImGuiToastPos : uint8_t -{ - TopLeft, - TopCenter, - TopRight, - BottomLeft, - BottomCenter, - BottomRight, - Center, - COUNT -}; - -/** - * @brief A class for creating toast notifications with ImGui. - */ -class ImGuiToast -{ -private: - ImGuiWindowFlags flags = NOTIFY_DEFAULT_TOAST_FLAGS; - - ImGuiToastType type = ImGuiToastType::None; - char title[NOTIFY_MAX_MSG_LENGTH]; - char content[NOTIFY_MAX_MSG_LENGTH]; - - int dismissTime = NOTIFY_DEFAULT_DISMISS; - std::chrono::system_clock::time_point creationTime = std::chrono::system_clock::now(); - - std::function onButtonPress = nullptr; // A lambda variable, which will be executed when button in notification is pressed - char buttonLabel[NOTIFY_MAX_MSG_LENGTH]; - -private: - // Setters - - inline void setTitle(const char* format, va_list args) - { - vsnprintf(this->title, sizeof(this->title), format, args); - } - - inline void setContent(const char* format, va_list args) - { - vsnprintf(this->content, sizeof(this->content), format, args); - } - - inline void setButtonLabel(const char* format, va_list args) - { - vsnprintf(this->buttonLabel, sizeof(this->buttonLabel), format, args); - } - -public: - - /** - * @brief Set the title of the toast notification. - * - * @param format The format string for the title. - * @param ... The arguments for the format string. - */ - inline void setTitle(const char* format, ...) - { - NOTIFY_FORMAT(this->setTitle, format); - } - - /** - * @brief Set the content of the toast notification. - * - * @param format The format string for the content. - * @param ... The arguments for the format string. - */ - inline void setContent(const char* format, ...) - { - NOTIFY_FORMAT(this->setContent, format); - } - - /** - * @brief Set the type of the toast notification. - * - * @param type The type of the toast notification. - */ - inline void setType(const ImGuiToastType& type) - { - IM_ASSERT(type < ImGuiToastType::COUNT); - this->type = type; - }; - - /** - * @brief Set the ImGui window flags for the notification. - * - * @param flags ImGui window flags to set. - */ - inline void setWindowFlags(const ImGuiWindowFlags& flags) - { - this->flags = flags; - } - - /** - * @brief Set the function to run on the button click in the notification. - * - * @param onButtonPress std::fuction or lambda expression, which contains the code for execution. - */ - inline void setOnButtonPress(const std::function& onButtonPress) - { - this->onButtonPress = onButtonPress; - } - - /** - * @brief Set the label for the button in the notification. - * - * @param format The format string for the label. - * @param ... The arguments for the format string. - */ - inline void setButtonLabel(const char* format, ...) - { - NOTIFY_FORMAT(this->setButtonLabel, format); - } - -public: - // Getters - - /** - * @brief Get the title of the toast notification. - * - * @return const char* The title of the toast notification. - */ - inline const char* getTitle() - { - return this->title; - }; - - /** - * @brief Get the default title of the toast notification based on its type. - * - * @return const char* The default title of the toast notification. - */ - inline const char* getDefaultTitle() - { - if (!strlen(this->title)) - { - switch (this->type) - { - case ImGuiToastType::None: - return nullptr; - case ImGuiToastType::Success: - return "Success"; - case ImGuiToastType::Warning: - return "Warning"; - case ImGuiToastType::Error: - return "Error"; - case ImGuiToastType::Info: - return "Info"; - default: - return nullptr; - } - } - - return this->title; - }; - - /** - * @brief Get the type of the toast notification. - * - * @return ImGuiToastType The type of the toast notification. - */ - inline ImGuiToastType getType() - { - return this->type; - }; - - /** - * @brief Get the color of the toast notification based on its type. - * - * @return ImVec4 The color of the toast notification. - */ - inline ImVec4 getColor() - { - switch (this->type) - { - case ImGuiToastType::None: - return {255, 255, 255, 255}; // White - case ImGuiToastType::Success: - return {0, 255, 0, 255}; // Green - case ImGuiToastType::Warning: - return {255, 255, 0, 255}; // Yellow - case ImGuiToastType::Error: - return {255, 0, 0, 255}; // Error - case ImGuiToastType::Info: - return {0, 157, 255, 255}; // Blue - default: - return {255, 255, 255, 255}; // White - } - } - - /** - * @brief Get the icon of the toast notification based on its type. - * - * @return const char* The icon of the toast notification. - */ - inline const char* getIcon() - { - switch (this->type) - { - case ImGuiToastType::None: - return nullptr; - case ImGuiToastType::Success: - return ICON_FA_CIRCLE_CHECK; // Font Awesome 6 - case ImGuiToastType::Warning: - return ICON_FA_TRIANGLE_EXCLAMATION; // Font Awesome 6 - case ImGuiToastType::Error: - return ICON_FA_CIRCLE_EXCLAMATION; // Font Awesome 6 - case ImGuiToastType::Info: - return ICON_FA_CIRCLE_INFO; // Font Awesome 6 - default: - return nullptr; - } - } - - /** - * @brief Get the content of the toast notification. - * - * @return char* The content of the toast notification. - */ - inline char* getContent() - { - return this->content; - }; - - /** - * @brief Get the elapsed time in milliseconds since the creation of the object. - * - * @return int64_t The elapsed time in milliseconds. - * @throws An exception with the message "Unsupported platform" if the platform is not supported. - */ - inline std::chrono::nanoseconds getElapsedTime() - { - return std::chrono::system_clock::now() - this->creationTime; - } - - /** - * @brief Get the current phase of the toast notification based on the elapsed time since its creation. - * - * @return ImGuiToastPhase The current phase of the toast notification. - * - ImGuiToastPhase::FadeIn: The notification is fading in. - * - ImGuiToastPhase::Wait: The notification is waiting to be dismissed. - * - ImGuiToastPhase::FadeOut: The notification is fading out. - * - ImGuiToastPhase::Expired: The notification has expired and should be removed. - */ - inline ImGuiToastPhase getPhase() - { - const int64_t elapsed = std::chrono::duration_cast(getElapsedTime()).count(); - - if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismissTime + NOTIFY_FADE_IN_OUT_TIME) - { - return ImGuiToastPhase::Expired; - } else - if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismissTime) - { - return ImGuiToastPhase::FadeOut; - } else - if (elapsed > NOTIFY_FADE_IN_OUT_TIME) - { - return ImGuiToastPhase::Wait; - } else - { - return ImGuiToastPhase::FadeIn; - } - } - - /** - * Returns the percentage of fade for the notification. - * @return The percentage of fade for the notification. - */ - inline float getFadePercent() - { - const ImGuiToastPhase phase = getPhase(); - const int64_t elapsed = std::chrono::duration_cast(getElapsedTime()).count(); - - if (phase == ImGuiToastPhase::FadeIn) - { - return ((float)elapsed / (float)NOTIFY_FADE_IN_OUT_TIME) * NOTIFY_OPACITY; - } else - if (phase == ImGuiToastPhase::FadeOut) - { - return (1.f - (((float)elapsed - (float)NOTIFY_FADE_IN_OUT_TIME - (float)this->dismissTime) / (float)NOTIFY_FADE_IN_OUT_TIME)) * NOTIFY_OPACITY; - } - - return 1.f * NOTIFY_OPACITY; - } - - /** - * @return ImGui window flags for the notification. - */ - inline ImGuiWindowFlags getWindowFlags() - { - return this->flags; - } - - /** - * @return The function, which is executed on the button click in the notification. - */ - inline std::function getOnButtonPress() - { - return this->onButtonPress; - } - - /** - * @return The label on the button in notification. - */ - inline const char* getButtonLabel() - { - return this->buttonLabel; - } - -public: - // Constructors - - /** - * @brief Creates a new ImGuiToast object with the specified type and dismiss time. - * - * @param type The type of the toast. - * @param dismissTime The time in milliseconds after which the toast should be dismissed. Default is NOTIFY_DEFAULT_DISMISS. - */ - ImGuiToast(ImGuiToastType type, int dismissTime = NOTIFY_DEFAULT_DISMISS) - { - IM_ASSERT(type < ImGuiToastType::COUNT); - - this->type = type; - this->dismissTime = dismissTime; - - this->creationTime = std::chrono::system_clock::now(); - - memset(this->title, 0, sizeof(this->title)); - memset(this->content, 0, sizeof(this->content)); - } - - /** - * @brief Constructor for creating an ImGuiToast object with a specified type and message format. - * - * @param type The type of the toast message. - * @param format The format string for the message. - * @param ... The variable arguments to be formatted according to the format string. - */ - ImGuiToast(ImGuiToastType type, const char* format, ...) : ImGuiToast(type) - { - NOTIFY_FORMAT(this->setContent, format); - } - - /** - * @brief Constructor for creating a new ImGuiToast object with a specified type, dismiss time, and content format. - * - * @param type The type of the toast message. - * @param dismissTime The time in milliseconds before the toast message is dismissed. - * @param format The format string for the content of the toast message. - * @param ... The variable arguments to be formatted according to the format string. - */ - ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...) : ImGuiToast(type, dismissTime) - { - NOTIFY_FORMAT(this->setContent, format); - } - - /** - * @brief Constructor for creating a new ImGuiToast object with a specified type, dismiss time, title format, content format and a button. - * - * @param type The type of the toast message. - * @param dismissTime The time in milliseconds before the toast message is dismissed. - * @param buttonLabel The label for the button. - * @param onButtonPress The lambda function to be executed when the button is pressed. - * @param format The format string for the content of the toast message. - * @param ... The variable arguments to be formatted according to the format string. - */ - ImGuiToast(ImGuiToastType type, int dismissTime, const char* buttonLabel, const std::function& onButtonPress, const char* format, ...) : ImGuiToast(type, dismissTime) - { - NOTIFY_FORMAT(this->setContent, format); - - this->onButtonPress = onButtonPress; - this->setButtonLabel(buttonLabel); - } -}; - -namespace ImGui -{ - inline std::vector notifications; - - /** - * Inserts a new notification into the notification queue. - * @param toast The notification to be inserted. - */ - inline void InsertNotification(const ImGuiToast& toast) - { - notifications.push_back(toast); - } - - /** - * @brief Removes a notification from the list of notifications. - * - * @param index The index of the notification to remove. - */ - inline void RemoveNotification(int index) - { - notifications.erase(notifications.begin() + index); - } - - /** - * Renders all notifications in the notifications vector. - * Each notification is rendered as a toast window with a title, content and an optional icon. - * If a notification is expired, it is removed from the vector. - */ - inline void RenderNotifications() - { - const ImVec2 mainWindowSize = GetMainViewport()->Size; - - float height = 0.f; - - for (size_t i = 0; i < notifications.size(); ++i) - { - ImGuiToast* currentToast = ¬ifications[i]; - - // Remove toast if expired - if (currentToast->getPhase() == ImGuiToastPhase::Expired) - { - RemoveNotification(i); - continue; - } - - #if NOTIFY_RENDER_LIMIT > 0 - if (i > NOTIFY_RENDER_LIMIT) - { - continue; - } - #endif - - // Get icon, title and other data - const char* icon = currentToast->getIcon(); - const char* title = currentToast->getTitle(); - const char* content = currentToast->getContent(); - const char* defaultTitle = currentToast->getDefaultTitle(); - const float opacity = currentToast->getFadePercent(); // Get opacity based of the current phase - - // Window rendering - ImVec4 textColor = currentToast->getColor(); - textColor.w = opacity; - - // Generate new unique name for this toast - char windowName[50]; - #ifdef _WIN32 - sprintf_s(windowName, "##TOAST%d", (int)i); - #elif defined(__linux__) || defined(__EMSCRIPTEN__) - std::sprintf(windowName, "##TOAST%d", (int)i); - #elif defined (__APPLE__) - std::snprintf(windowName, 50, "##TOAST%d", (int)i); - #else - throw "Unsupported platform"; - #endif - - //PushStyleColor(ImGuiCol_Text, textColor); - SetNextWindowBgAlpha(opacity); - - #if NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW - short mainMonitorId = static_cast(GetMainViewport())->PlatformMonitor; - - ImGuiPlatformIO& platformIO = GetPlatformIO(); - ImGuiPlatformMonitor& monitor = platformIO.Monitors[mainMonitorId]; - - // Set notification window position to bottom right corner of the monitor - SetNextWindowPos(ImVec2(monitor.WorkPos.x + monitor.WorkSize.x - NOTIFY_PADDING_X, monitor.WorkPos.y + monitor.WorkSize.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f)); - #else - // Set notification window position to bottom right corner of the main window, considering the main window size and location in relation to the display - ImVec2 mainWindowPos = GetMainViewport()->Pos; - SetNextWindowPos(ImVec2(mainWindowPos.x + mainWindowSize.x - NOTIFY_PADDING_X, mainWindowPos.y + mainWindowSize.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f)); - #endif - - // Set notification window flags - if (!NOTIFY_USE_DISMISS_BUTTON && currentToast->getOnButtonPress() == nullptr) - { - currentToast->setWindowFlags(NOTIFY_DEFAULT_TOAST_FLAGS | ImGuiWindowFlags_NoInputs); - } - - Begin(windowName, nullptr, currentToast->getWindowFlags()); - - // Render over all other windows - BringWindowToDisplayFront(GetCurrentWindow()); - - // Here we render the toast content - { - PushTextWrapPos(mainWindowSize.x / 3.f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width - - bool wasTitleRendered = false; - - // If an icon is set - if (!NOTIFY_NULL_OR_EMPTY(icon)) - { - //Text(icon); // Render icon text - TextColored(textColor, "%s", icon); - wasTitleRendered = true; - } - - // If a title is set - if (!NOTIFY_NULL_OR_EMPTY(title)) - { - // If a title and an icon is set, we want to render on same line - if (!NOTIFY_NULL_OR_EMPTY(icon)) - SameLine(); - - Text("%s", title); // Render title text - wasTitleRendered = true; - } else - if (!NOTIFY_NULL_OR_EMPTY(defaultTitle)) - { - if (!NOTIFY_NULL_OR_EMPTY(icon)) - SameLine(); - - Text("%s", defaultTitle); // Render default title text (ImGuiToastType_Success -> "Success", etc...) - wasTitleRendered = true; - } - - // If a dismiss button is enabled - if (NOTIFY_USE_DISMISS_BUTTON) - { - // If a title or content is set, we want to render the button on the same line - if (wasTitleRendered || !NOTIFY_NULL_OR_EMPTY(content)) - { - SameLine(); - } - - // Render the dismiss button on the top right corner - // NEEDS TO BE REWORKED - float scale = 0.8f; - - if (CalcTextSize(content).x > GetContentRegionAvail().x) - { - scale = 0.8f; - } - - SetCursorPosX(GetCursorPosX() + (GetWindowSize().x - GetCursorPosX()) * scale); - - // If the button is pressed, we want to remove the notification - if (Button(ICON_FA_XMARK)) - { - RemoveNotification(i); - } - } - - // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically - if (wasTitleRendered && !NOTIFY_NULL_OR_EMPTY(content)) - { - SetCursorPosY(GetCursorPosY() + 5.f); // Must be a better way to do this!!!! - } - - // If a content is set - if (!NOTIFY_NULL_OR_EMPTY(content)) - { - if (wasTitleRendered) - { - #if NOTIFY_USE_SEPARATOR - Separator(); - #endif - } - - Text("%s", content); // Render content text - } - - // If a button is set - if (currentToast->getOnButtonPress() != nullptr) - { - // If the button is pressed, we want to execute the lambda function - if (Button(currentToast->getButtonLabel())) - { - currentToast->getOnButtonPress()(); - } - } - - PopTextWrapPos(); - } - - // Save height for next toasts - height += GetWindowHeight() + NOTIFY_PADDING_MESSAGE_Y; - - // End - End(); - } - } -} - -#endif \ No newline at end of file diff --git a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfo.cs b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfo.cs new file mode 100644 index 00000000..deef3787 --- /dev/null +++ b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfo.cs @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Elgar\'nan.Tests")] +[assembly: System.Reflection.AssemblyCompanyAttribute("pieckenst")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyDescriptionAttribute("Dynamic plugin loading system for WebLaunch game launcher platform. Supports hot " + + "reloading, dependency management, and isolated plugin execution.")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+158887506bc8d130056c6e93f52a341c33b7d4dd")] +[assembly: System.Reflection.AssemblyProductAttribute("Elgar\'nan")] +[assembly: System.Reflection.AssemblyTitleAttribute("Elgar\'nan is a dynamic plugin loading system for WebLaunch game launcher platform" + + ". ")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfoInputs.cache b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfoInputs.cache new file mode 100644 index 00000000..6dd3209b --- /dev/null +++ b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +f4b83cee663df70200f60eb7134658b34c064f3cd7ae0d3ebd4ee12d9c31ff64 diff --git a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.GeneratedMSBuildEditorConfig.editorconfig b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..4a5bb3a3 --- /dev/null +++ b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,25 @@ +is_global = true +build_property.EnableAotAnalyzer = +build_property.EnableSingleFileAnalyzer = true +build_property.EnableTrimAnalyzer = +build_property.IncludeAllContentForSelfExtract = True +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = El_Garnan_Plugin_Loader +build_property.ProjectDir = G:\code\WebLaunch\El'GarnanPluginSystem\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.PolySharpUsePublicAccessibilityForGeneratedTypes = +build_property.PolySharpIncludeRuntimeSupportedAttributes = +build_property.PolySharpUseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute = +build_property.PolySharpExcludeGeneratedTypes = +build_property.PolySharpIncludeGeneratedTypes = +build_property.PolySharpExcludeTypeForwardedToDeclarations = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.assets.cache b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.assets.cache index 60b6f78b..69204e76 100644 Binary files a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.assets.cache and b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.assets.cache differ diff --git a/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.csproj.AssemblyReference.cache b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.csproj.AssemblyReference.cache new file mode 100644 index 00000000..b99820bc Binary files /dev/null and b/El'GarnanPluginSystem/obj/Debug/net9.0/El'GarnanPluginSystem.csproj.AssemblyReference.cache differ