diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index feed79772..a35248062 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -380,7 +380,7 @@ namespace Dalamud.Injector startInfo.BootShowConsole = args.Contains("--console"); startInfo.BootEnableEtw = args.Contains("--etw"); startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName); - startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "clr_failfast_hijack", "prevent_icmphandle_crashes" }; + startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "prevent_icmphandle_crashes" }; startInfo.BootDotnetOpenProcessHookMode = 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0; diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 8c0a33081..a4c81c7a7 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; @@ -16,9 +17,11 @@ using JetBrains.Annotations; using Lumina; using Lumina.Data; using Lumina.Data.Files; +using Lumina.Data.Parsing.Tex.Buffers; using Lumina.Excel; using Newtonsoft.Json; using Serilog; +using SharpDX.DXGI; namespace Dalamud.Data; @@ -267,9 +270,31 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager /// [Obsolete("Use ITextureProvider instead")] + [return: NotNullIfNotNull(nameof(tex))] public TextureWrap? GetImGuiTexture(TexFile? tex) - => tex == null ? null : Service.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4); + { + if (tex is null) + return null; + var im = Service.Get(); + var buffer = tex.TextureBuffer; + var bpp = 1 << (((int)tex.Header.Format & (int)TexFile.TextureFormat.BppMask) >> + (int)TexFile.TextureFormat.BppShift); + + var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(tex.Header.Format, false); + if (conversion != TexFile.DxgiFormatConversion.NoConversion || !im.SupportsDxgiFormat((Format)dxgiFormat)) + { + dxgiFormat = (int)Format.B8G8R8A8_UNorm; + buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8); + bpp = 32; + } + + var pitch = buffer is BlockCompressionTextureBuffer + ? Math.Max(1, (buffer.Width + 3) / 4) * 2 * bpp + : ((buffer.Width * bpp) + 7) / 8; + return im.LoadImageFromDxgiFormat(buffer.RawData, pitch, buffer.Width, buffer.Height, (Format)dxgiFormat); + } + /// [Obsolete("Use ITextureProvider instead")] public TextureWrap? GetImGuiTexture(string path) diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index cdd1515b0..ee8418362 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -26,52 +26,52 @@ public unsafe class Character : GameObject /// /// Gets the current HP of this Chara. /// - public uint CurrentHp => this.Struct->Health; + public uint CurrentHp => this.Struct->CharacterData.Health; /// /// Gets the maximum HP of this Chara. /// - public uint MaxHp => this.Struct->MaxHealth; + public uint MaxHp => this.Struct->CharacterData.MaxHealth; /// /// Gets the current MP of this Chara. /// - public uint CurrentMp => this.Struct->Mana; + public uint CurrentMp => this.Struct->CharacterData.Mana; /// /// Gets the maximum MP of this Chara. /// - public uint MaxMp => this.Struct->MaxMana; + public uint MaxMp => this.Struct->CharacterData.MaxMana; /// /// Gets the current GP of this Chara. /// - public uint CurrentGp => this.Struct->GatheringPoints; + public uint CurrentGp => this.Struct->CharacterData.GatheringPoints; /// /// Gets the maximum GP of this Chara. /// - public uint MaxGp => this.Struct->MaxGatheringPoints; + public uint MaxGp => this.Struct->CharacterData.MaxGatheringPoints; /// /// Gets the current CP of this Chara. /// - public uint CurrentCp => this.Struct->CraftingPoints; + public uint CurrentCp => this.Struct->CharacterData.CraftingPoints; /// /// Gets the maximum CP of this Chara. /// - public uint MaxCp => this.Struct->MaxCraftingPoints; + public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints; /// /// Gets the ClassJob of this Chara. /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); + public ExcelResolver ClassJob => new(this.Struct->CharacterData.ClassJob); /// /// Gets the level of this Chara. /// - public byte Level => this.Struct->Level; + public byte Level => this.Struct->CharacterData.Level; /// /// Gets a byte array describing the visual appearance of this Chara. @@ -97,7 +97,7 @@ public unsafe class Character : GameObject /// /// Gets the current online status of the character. /// - public ExcelResolver OnlineStatus => new(this.Struct->OnlineStatus); + public ExcelResolver OnlineStatus => new(this.Struct->CharacterData.OnlineStatus); /// /// Gets the status flags. diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index d53a29f25..841511f55 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -26,6 +26,10 @@ using ImGuiNET; using ImGuiScene; using PInvoke; using Serilog; +using SharpDX; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using SharpDX.DXGI; // general dev notes, here because it's easiest @@ -303,6 +307,62 @@ internal class InterfaceManager : IDisposable, IServiceType return null; } + /// + /// Check whether the current D3D11 Device supports the given DXGI format. + /// + /// DXGI format to check. + /// Whether it is supported. + public bool SupportsDxgiFormat(Format dxgiFormat) => this.scene is null + ? throw new InvalidOperationException("Scene isn't ready.") + : this.scene.Device.CheckFormatSupport(dxgiFormat).HasFlag(FormatSupport.Texture2D); + + /// + /// Load an image from a span of bytes of specified format. + /// + /// The data to load. + /// The pitch(stride) in bytes. + /// The width in pixels. + /// The height in pixels. + /// Format of the texture. + /// A texture, ready to use in ImGui. + public TextureWrap LoadImageFromDxgiFormat(Span data, int pitch, int width, int height, Format dxgiFormat) + { + if (this.scene == null) + throw new InvalidOperationException("Scene isn't ready."); + + ShaderResourceView resView; + unsafe + { + fixed (void* pData = data) + { + var texDesc = new Texture2DDescription + { + Width = width, + Height = height, + MipLevels = 1, + ArraySize = 1, + Format = dxgiFormat, + SampleDescription = new(1, 0), + Usage = ResourceUsage.Immutable, + BindFlags = BindFlags.ShaderResource, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None, + }; + + using var texture = new Texture2D(this.Device, texDesc, new DataRectangle(new(pData), pitch)); + resView = new(this.Device, texture, new() + { + Format = texDesc.Format, + Dimension = ShaderResourceViewDimension.Texture2D, + Texture2D = { MipLevels = texDesc.MipLevels }, + }); + } + } + + // no sampler for now because the ImGui implementation we copied doesn't allow for changing it + return new D3DTextureWrap(resView, width, height); + } + #nullable restore /// diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index feac634f3..db748303e 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -12,6 +12,9 @@ namespace Dalamud.IoC.Internal; /// /// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. +/// +/// This is only used to resolve dependencies for plugins. +/// Dalamud services are constructed via Service{T}.ConstructObject at the moment. /// internal class ServiceContainer : IServiceProvider, IServiceType { @@ -31,7 +34,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType /// Register a singleton object of any type into the current IOC container. /// /// The existing instance to register in the container. - /// The interface to register. + /// The type to register. public void RegisterSingleton(Task instance) { if (instance == null) @@ -40,19 +43,27 @@ internal class ServiceContainer : IServiceProvider, IServiceType } this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T)); + this.RegisterInterfaces(typeof(T)); + } - var resolveViaTypes = typeof(T) - .GetCustomAttributes() - .OfType() - .Select(x => x.GetType().GetGenericArguments().First()); + /// + /// Register the interfaces that can resolve this type. + /// + /// The type to register. + public void RegisterInterfaces(Type type) + { + var resolveViaTypes = type + .GetCustomAttributes() + .OfType() + .Select(x => x.GetType().GetGenericArguments().First()); foreach (var resolvableType in resolveViaTypes) { - Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", typeof(T).FullName ?? "???"); + Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", type.FullName ?? "???"); Debug.Assert(!this.interfaceToTypeMap.ContainsKey(resolvableType), "A service already implements this interface, this is not allowed"); - Debug.Assert(typeof(T).IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type"); + Debug.Assert(type.IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type"); - this.interfaceToTypeMap[resolvableType] = typeof(T); + this.interfaceToTypeMap[resolvableType] = type; } } @@ -95,18 +106,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType parameters .Select(async p => { - if (p.parameterType.GetCustomAttribute() != null) - { - if (scopeImpl == null) - { - Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!); - return null; - } - - return await scopeImpl.CreatePrivateScopedObject(p.parameterType, scopedObjects); - } - - var service = await this.GetService(p.parameterType, scopedObjects); + var service = await this.GetService(p.parameterType, scopeImpl, scopedObjects); if (service == null) { @@ -168,22 +168,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType foreach (var prop in props) { - object service = null; - - if (prop.propertyInfo.PropertyType.GetCustomAttribute() != null) - { - if (scopeImpl == null) - { - Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!); - } - else - { - service = await scopeImpl.CreatePrivateScopedObject(prop.propertyInfo.PropertyType, publicScopes); - } - } - - service ??= await this.GetService(prop.propertyInfo.PropertyType, publicScopes); - + var service = await this.GetService(prop.propertyInfo.PropertyType, scopeImpl, publicScopes); if (service == null) { Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!); @@ -203,7 +188,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType public IServiceScope GetScope() => new ServiceScopeImpl(this); /// - object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); + object? IServiceProvider.GetService(Type serviceType) => this.GetSingletonService(serviceType); private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) { @@ -228,9 +213,23 @@ internal class ServiceContainer : IServiceProvider, IServiceType return false; } - private async Task GetService(Type serviceType, object[] scopedObjects) + private async Task GetService(Type serviceType, ServiceScopeImpl? scope, object[] scopedObjects) { - var singletonService = await this.GetService(serviceType); + if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) + serviceType = implementingType; + + if (serviceType.GetCustomAttribute() != null) + { + if (scope == null) + { + Log.Error("Failed to create {TypeName}, is scoped but no scope provided", serviceType.FullName!); + return null; + } + + return await scope.CreatePrivateScopedObject(serviceType, scopedObjects); + } + + var singletonService = await this.GetSingletonService(serviceType, false); if (singletonService != null) { return singletonService; @@ -246,9 +245,9 @@ internal class ServiceContainer : IServiceProvider, IServiceType return scoped; } - private async Task GetService(Type serviceType) + private async Task GetSingletonService(Type serviceType, bool tryGetInterface = true) { - if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) + if (tryGetInterface && this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) serviceType = implementingType; if (!this.instances.TryGetValue(serviceType, out var service)) @@ -285,13 +284,24 @@ internal class ServiceContainer : IServiceProvider, IServiceType private bool ValidateCtor(ConstructorInfo ctor, Type[] types) { + bool IsTypeValid(Type type) + { + var contains = types.Any(x => x.IsAssignableTo(type)); + + // Scoped services are created on-demand + return contains || type.GetCustomAttribute() != null; + } + var parameters = ctor.GetParameters(); foreach (var parameter in parameters) { - var contains = types.Any(x => x.IsAssignableTo(parameter.ParameterType)); + var valid = IsTypeValid(parameter.ParameterType); + + // If this service is provided by an interface + if (!valid && this.interfaceToTypeMap.TryGetValue(parameter.ParameterType, out var implementationType)) + valid = IsTypeValid(implementationType); - // Scoped services are created on-demand - if (!contains && parameter.ParameterType.GetCustomAttribute() == null) + if (!valid) { Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!); return false; diff --git a/Dalamud/Plugin/Services/IDataManager.cs b/Dalamud/Plugin/Services/IDataManager.cs index 4f08cf618..ff9b40605 100644 --- a/Dalamud/Plugin/Services/IDataManager.cs +++ b/Dalamud/Plugin/Services/IDataManager.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using ImGuiScene; using Lumina; @@ -147,6 +148,7 @@ public interface IDataManager /// The Lumina . /// A that can be used to draw the texture. [Obsolete("Use ITextureProvider instead")] + [return: NotNullIfNotNull(nameof(tex))] public TextureWrap? GetImGuiTexture(TexFile? tex); /// diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs index 41ffe33ca..d1c1002bd 100644 --- a/Dalamud/ServiceManager.cs +++ b/Dalamud/ServiceManager.cs @@ -132,11 +132,20 @@ internal static class ServiceManager var dependencyServicesMap = new Dictionary>(); var getAsyncTaskMap = new Dictionary(); + var serviceContainer = Service.Get(); + foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes()) { var serviceKind = serviceType.GetServiceKind(); - if (serviceKind is ServiceKind.None or ServiceKind.ScopedService) + if (serviceKind is ServiceKind.None) continue; + + // Scoped service do not go through Service, so we must let ServiceContainer know what their interfaces map to + if (serviceKind is ServiceKind.ScopedService) + { + serviceContainer.RegisterInterfaces(serviceType); + continue; + } Debug.Assert( !serviceKind.HasFlag(ServiceKind.ManualService) && !serviceKind.HasFlag(ServiceKind.ScopedService), diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 8962a47b9..93db21d9b 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 8962a47b95f96bec53e58680bd9d1e7f38610d40 +Subproject commit 93db21d9b6fb5cc671cd25c79a6ac933f3ca6710