mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge branch 'master' into net8
This commit is contained in:
commit
009151820d
32 changed files with 1612 additions and 573 deletions
43
.github/workflows/main.yml
vendored
43
.github/workflows/main.yml
vendored
|
|
@ -45,7 +45,48 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: dalamud-artifact
|
name: dalamud-artifact
|
||||||
path: bin\Release
|
path: bin\Release
|
||||||
|
|
||||||
|
check_api_compat:
|
||||||
|
name: "Check API Compatibility"
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
needs: build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: "Install .NET SDK"
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 7
|
||||||
|
- name: "Install ApiCompat"
|
||||||
|
run: |
|
||||||
|
dotnet tool install -g Microsoft.DotNet.ApiCompat.Tool
|
||||||
|
- name: "Download Proposed Artifacts"
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dalamud-artifact
|
||||||
|
path: .\right
|
||||||
|
- name: "Download Live (Stg) Artifacts"
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
|
||||||
|
Expand-Archive -Force latest.zip "left"
|
||||||
|
- name: "Verify Compatibility"
|
||||||
|
run: |
|
||||||
|
$FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll"
|
||||||
|
|
||||||
|
$retcode = 0
|
||||||
|
|
||||||
|
foreach ($file in $FILES_TO_VALIDATE) {
|
||||||
|
$testout = ""
|
||||||
|
Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ==="
|
||||||
|
apicompat -l "left\${file}" -r "right\${file}" | Tee-Object -Variable testout
|
||||||
|
Write-Output "::endgroup::"
|
||||||
|
if ($testout -ne "APICompat ran successfully without finding any breaking changes.") {
|
||||||
|
Write-Output "::error::${file} did not pass. Please review it for problems."
|
||||||
|
$retcode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $retcode
|
||||||
|
|
||||||
deploy_stg:
|
deploy_stg:
|
||||||
name: Deploy dalamud-distrib staging
|
name: Deploy dalamud-distrib staging
|
||||||
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
||||||
using Dalamud.Common;
|
using Dalamud.Common;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Gui.Internal;
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
|
|
@ -178,7 +177,7 @@ internal sealed class Dalamud : IServiceType
|
||||||
// this must be done before unloading interface manager, in order to do rebuild
|
// this must be done before unloading interface manager, in order to do rebuild
|
||||||
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
|
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
|
||||||
// will not receive any windows messages
|
// will not receive any windows messages
|
||||||
Service<DalamudIME>.GetNullable()?.Dispose();
|
Service<DalamudIme>.GetNullable()?.Dispose();
|
||||||
|
|
||||||
// this must be done before unloading plugins, or it can cause a race condition
|
// this must be done before unloading plugins, or it can cause a race condition
|
||||||
// due to rendering happening on another thread, where a plugin might receive
|
// due to rendering happening on another thread, where a plugin might receive
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<DalamudVersion>9.0.0.14</DalamudVersion>
|
<DalamudVersion>9.0.0.17</DalamudVersion>
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,10 @@ internal unsafe class AddonEventListener : IDisposable
|
||||||
{
|
{
|
||||||
if (node is null) return;
|
if (node is null) return;
|
||||||
|
|
||||||
node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
|
Service<Framework>.Get().RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -80,7 +83,10 @@ internal unsafe class AddonEventListener : IDisposable
|
||||||
{
|
{
|
||||||
if (node is null) return;
|
if (node is null) return;
|
||||||
|
|
||||||
node->RemoveEvent(eventType, param, this.eventListener, false);
|
Service<Framework>.Get().RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
node->RemoveEvent(eventType, param, this.eventListener, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly]
|
[UnmanagedCallersOnly]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
@ -9,7 +8,6 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
@ -26,22 +24,19 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PluginName for Dalamud Internal use.
|
/// PluginName for Dalamud Internal use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DalamudInternalKey = "Dalamud.Internal";
|
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
|
||||||
|
|
||||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||||
|
|
||||||
private readonly AddonEventManagerAddressResolver address;
|
private readonly AddonEventManagerAddressResolver address;
|
||||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||||
|
|
||||||
private readonly List<PluginEventController> pluginEventControllers;
|
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||||
|
|
||||||
private AddonCursorType? cursorOverride;
|
private AddonCursorType? cursorOverride;
|
||||||
|
|
||||||
|
|
@ -51,10 +46,8 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
this.address = new AddonEventManagerAddressResolver();
|
this.address = new AddonEventManagerAddressResolver();
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
this.pluginEventControllers = new List<PluginEventController>
|
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||||
{
|
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||||
new(DalamudInternalKey), // Create entry for Dalamud's Internal Use.
|
|
||||||
};
|
|
||||||
|
|
||||||
this.cursorOverride = null;
|
this.cursorOverride = null;
|
||||||
|
|
||||||
|
|
@ -73,7 +66,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
this.onUpdateCursor.Dispose();
|
this.onUpdateCursor.Dispose();
|
||||||
|
|
||||||
foreach (var pluginEventController in this.pluginEventControllers)
|
foreach (var (_, pluginEventController) in this.pluginEventControllers)
|
||||||
{
|
{
|
||||||
pluginEventController.Dispose();
|
pluginEventController.Dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -90,16 +83,17 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
/// <param name="eventType">The event type for this event.</param>
|
/// <param name="eventType">The event type for this event.</param>
|
||||||
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||||
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||||
internal IAddonEventHandle? AddEvent(string pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||||
{
|
{
|
||||||
if (!ThreadSafety.IsMainThread) throw new InvalidOperationException("This should be done only from the main thread. Modifying active native code on non-main thread is not supported.");
|
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
|
||||||
|
|
||||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
|
|
||||||
{
|
{
|
||||||
return eventController.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
|
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,13 +102,11 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
/// <param name="eventHandle">The Unique Id for this event.</param>
|
/// <param name="eventHandle">The Unique Id for this event.</param>
|
||||||
internal void RemoveEvent(string pluginId, IAddonEventHandle eventHandle)
|
internal void RemoveEvent(Guid pluginId, IAddonEventHandle eventHandle)
|
||||||
{
|
{
|
||||||
if (!ThreadSafety.IsMainThread) throw new InvalidOperationException("This should be done only from the main thread. Modifying active native code on non-main thread is not supported.");
|
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
|
||||||
|
|
||||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
|
|
||||||
{
|
{
|
||||||
eventController.RemoveEvent(eventHandle);
|
controller.RemoveEvent(eventHandle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -137,33 +129,28 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
/// Adds a new managed event controller if one doesn't already exist for this pluginId.
|
/// Adds a new managed event controller if one doesn't already exist for this pluginId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
internal void AddPluginEventController(string pluginId)
|
internal void AddPluginEventController(Guid pluginId)
|
||||||
{
|
{
|
||||||
this.framework.RunOnFrameworkThread(() =>
|
this.pluginEventControllers.GetOrAdd(
|
||||||
{
|
pluginId,
|
||||||
if (this.pluginEventControllers.All(entry => entry.PluginId != pluginId))
|
key =>
|
||||||
{
|
{
|
||||||
Log.Verbose($"Creating new PluginEventController for: {pluginId}");
|
Log.Verbose($"Creating new PluginEventController for: {key}");
|
||||||
this.pluginEventControllers.Add(new PluginEventController(pluginId));
|
return new PluginEventController();
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes an existing managed event controller for the specified plugin.
|
/// Removes an existing managed event controller for the specified plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
internal void RemovePluginEventController(string pluginId)
|
internal void RemovePluginEventController(Guid pluginId)
|
||||||
{
|
{
|
||||||
this.framework.RunOnFrameworkThread(() =>
|
if (this.pluginEventControllers.TryRemove(pluginId, out var controller))
|
||||||
{
|
{
|
||||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } controller)
|
Log.Verbose($"Removing PluginEventController for: {pluginId}");
|
||||||
{
|
controller.Dispose();
|
||||||
Log.Verbose($"Removing PluginEventController for: {pluginId}");
|
}
|
||||||
this.pluginEventControllers.Remove(controller);
|
|
||||||
controller.Dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -178,7 +165,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
|
|
||||||
foreach (var pluginList in this.pluginEventControllers)
|
foreach (var pluginList in this.pluginEventControllers)
|
||||||
{
|
{
|
||||||
pluginList.RemoveForAddon(addonInfo.AddonName);
|
pluginList.Value.RemoveForAddon(addonInfo.AddonName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +221,7 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon
|
||||||
{
|
{
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
this.eventManagerService.AddPluginEventController(plugin.Manifest.WorkingPluginId.ToString());
|
this.eventManagerService.AddPluginEventController(plugin.Manifest.WorkingPluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -246,16 +233,16 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon
|
||||||
this.eventManagerService.ResetCursor();
|
this.eventManagerService.ResetCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventManagerService.RemovePluginEventController(this.plugin.Manifest.WorkingPluginId.ToString());
|
this.eventManagerService.RemovePluginEventController(this.plugin.Manifest.WorkingPluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||||
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId.ToString(), atkUnitBase, atkResNode, eventType, eventHandler);
|
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void RemoveEvent(IAddonEventHandle eventHandle)
|
public void RemoveEvent(IAddonEventHandle eventHandle)
|
||||||
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId.ToString(), eventHandle);
|
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId, eventHandle);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetCursor(AddonCursorType cursor)
|
public void SetCursor(AddonCursorType cursor)
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,11 @@ internal unsafe class PluginEventController : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginId">The Unique ID for this plugin.</param>
|
public PluginEventController()
|
||||||
public PluginEventController(string pluginId)
|
|
||||||
{
|
{
|
||||||
this.PluginId = pluginId;
|
|
||||||
|
|
||||||
this.EventListener = new AddonEventListener(this.PluginEventListHandler);
|
this.EventListener = new AddonEventListener(this.PluginEventListHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unique ID for this PluginEventList.
|
|
||||||
/// </summary>
|
|
||||||
public string PluginId { get; init; }
|
|
||||||
|
|
||||||
private AddonEventListener EventListener { get; init; }
|
private AddonEventListener EventListener { get; init; }
|
||||||
|
|
||||||
private List<AddonEventEntry> Events { get; } = new();
|
private List<AddonEventEntry> Events { get; } = new();
|
||||||
|
|
@ -125,7 +117,7 @@ internal unsafe class PluginEventController : IDisposable
|
||||||
if (this.Events.All(registeredEvent => registeredEvent.ParamKey != i)) return i;
|
if (this.Events.All(registeredEvent => registeredEvent.ParamKey != i)) return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new OverflowException($"uint.MaxValue number of ParamKeys used for {this.PluginId}");
|
throw new OverflowException($"uint.MaxValue number of ParamKeys used for this event controller.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -14,9 +15,9 @@ using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.Internal.Windows;
|
using Dalamud.Interface.Internal.Windows;
|
||||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
|
@ -60,6 +61,8 @@ internal class ChatHandlers : IServiceType
|
||||||
// { XivChatType.Echo, Color.Gray },
|
// { XivChatType.Echo, Color.Gray },
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("CHATHANDLER");
|
||||||
|
|
||||||
private readonly Regex rmtRegex = new(
|
private readonly Regex rmtRegex = new(
|
||||||
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
@ -110,6 +113,7 @@ internal class ChatHandlers : IServiceType
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
private bool startedAutoUpdatingPlugins;
|
private bool startedAutoUpdatingPlugins;
|
||||||
|
private CancellationTokenSource deferredAutoUpdateCts = new();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private ChatHandlers(ChatGui chatGui)
|
private ChatHandlers(ChatGui chatGui)
|
||||||
|
|
@ -165,16 +169,19 @@ internal class ChatHandlers : IServiceType
|
||||||
if (clientState == null)
|
if (clientState == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
if (type == XivChatType.Notice)
|
||||||
this.PrintWelcomeMessage();
|
{
|
||||||
|
if (!this.hasSeenLoadingMsg)
|
||||||
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
|
if (!this.startedAutoUpdatingPlugins)
|
||||||
|
this.AutoUpdatePluginsWithRetry();
|
||||||
|
}
|
||||||
|
|
||||||
// For injections while logged in
|
// For injections while logged in
|
||||||
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||||
this.PrintWelcomeMessage();
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
if (!this.startedAutoUpdatingPlugins)
|
|
||||||
this.AutoUpdatePlugins();
|
|
||||||
|
|
||||||
#if !DEBUG && false
|
#if !DEBUG && false
|
||||||
if (!this.hasSeenLoadingMsg)
|
if (!this.hasSeenLoadingMsg)
|
||||||
return;
|
return;
|
||||||
|
|
@ -264,24 +271,42 @@ internal class ChatHandlers : IServiceType
|
||||||
this.hasSeenLoadingMsg = true;
|
this.hasSeenLoadingMsg = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoUpdatePlugins()
|
private void AutoUpdatePluginsWithRetry()
|
||||||
|
{
|
||||||
|
var firstAttempt = this.AutoUpdatePlugins();
|
||||||
|
if (!firstAttempt)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Task.Delay(30_000, this.deferredAutoUpdateCts.Token);
|
||||||
|
this.AutoUpdatePlugins();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AutoUpdatePlugins()
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.GetNullable();
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
var pluginManager = Service<PluginManager>.GetNullable();
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
var notifications = Service<NotificationManager>.GetNullable();
|
var notifications = Service<NotificationManager>.GetNullable();
|
||||||
|
|
||||||
if (chatGui == null || pluginManager == null || notifications == null)
|
if (chatGui == null || pluginManager == null || notifications == null)
|
||||||
return;
|
{
|
||||||
|
Log.Warning("Aborting auto-update because a required service was not loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
|
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
|
||||||
{
|
{
|
||||||
// Plugins aren't ready yet.
|
// Plugins aren't ready yet.
|
||||||
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
|
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
|
||||||
return;
|
Log.Warning("Aborting auto-update because plugins weren't loaded or ready.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startedAutoUpdatingPlugins = true;
|
this.startedAutoUpdatingPlugins = true;
|
||||||
|
|
||||||
|
Log.Debug("Beginning plugin auto-update process...");
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
|
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
this.IsAutoUpdateComplete = true;
|
this.IsAutoUpdateComplete = true;
|
||||||
|
|
@ -320,5 +345,7 @@ internal class ChatHandlers : IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,301 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Interface.Internal;
|
|
||||||
using Dalamud.Logging.Internal;
|
|
||||||
using ImGuiNET;
|
|
||||||
using PInvoke;
|
|
||||||
|
|
||||||
using static Dalamud.NativeFunctions;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.Internal;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class handles IME for non-English users.
|
|
||||||
/// </summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
internal unsafe class DalamudIME : IDisposable, IServiceType
|
|
||||||
{
|
|
||||||
private static readonly ModuleLog Log = new("IME");
|
|
||||||
|
|
||||||
private AsmHook imguiTextInputCursorHook;
|
|
||||||
private Vector2* cursorPos;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private DalamudIME()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the module is enabled.
|
|
||||||
/// </summary>
|
|
||||||
internal bool IsEnabled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the first imm candidate in relation to the full list.
|
|
||||||
/// </summary>
|
|
||||||
internal CandidateList ImmCandNative { get; private set; } = default;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the imm candidates.
|
|
||||||
/// </summary>
|
|
||||||
internal List<string> ImmCand { get; private set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the selected imm component.
|
|
||||||
/// </summary>
|
|
||||||
internal string ImmComp { get; private set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.imguiTextInputCursorHook?.Dispose();
|
|
||||||
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes window messages.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hWnd">Handle of the window.</param>
|
|
||||||
/// <param name="msg">Type of window message.</param>
|
|
||||||
/// <param name="wParamPtr">wParam or the pointer to it.</param>
|
|
||||||
/// <param name="lParamPtr">lParam or the pointer to it.</param>
|
|
||||||
/// <returns>Return value, if not doing further processing.</returns>
|
|
||||||
public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParamPtr, void* lParamPtr)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
|
||||||
{
|
|
||||||
var io = ImGui.GetIO();
|
|
||||||
var wmsg = (WindowsMessage)msg;
|
|
||||||
long wParam = (long)wParamPtr, lParam = (long)lParamPtr;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
wParam = Marshal.ReadInt32((IntPtr)wParamPtr);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lParam = Marshal.ReadInt32((IntPtr)lParamPtr);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (wmsg)
|
|
||||||
{
|
|
||||||
case WindowsMessage.WM_IME_NOTIFY:
|
|
||||||
switch ((IMECommand)(IntPtr)wParam)
|
|
||||||
{
|
|
||||||
case IMECommand.ChangeCandidate:
|
|
||||||
this.ToggleWindow(true);
|
|
||||||
this.LoadCand(hWnd);
|
|
||||||
break;
|
|
||||||
case IMECommand.OpenCandidate:
|
|
||||||
this.ToggleWindow(true);
|
|
||||||
this.ImmCandNative = default;
|
|
||||||
// this.ImmCand.Clear();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IMECommand.CloseCandidate:
|
|
||||||
this.ToggleWindow(false);
|
|
||||||
this.ImmCandNative = default;
|
|
||||||
// this.ImmCand.Clear();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case WindowsMessage.WM_IME_COMPOSITION:
|
|
||||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
|
||||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0)
|
|
||||||
{
|
|
||||||
var hIMC = ImmGetContext(hWnd);
|
|
||||||
if (hIMC == IntPtr.Zero)
|
|
||||||
return IntPtr.Zero;
|
|
||||||
|
|
||||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
|
||||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
|
||||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
|
||||||
|
|
||||||
var bytes = new byte[dwSize];
|
|
||||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
|
||||||
Marshal.FreeHGlobal(unmanagedPointer);
|
|
||||||
|
|
||||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
|
||||||
this.ImmComp = lpstr;
|
|
||||||
if (lpstr == string.Empty)
|
|
||||||
{
|
|
||||||
this.ToggleWindow(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.LoadCand(hWnd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0)
|
|
||||||
{
|
|
||||||
var hIMC = ImmGetContext(hWnd);
|
|
||||||
if (hIMC == IntPtr.Zero)
|
|
||||||
return IntPtr.Zero;
|
|
||||||
|
|
||||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
|
||||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
|
||||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
|
||||||
|
|
||||||
var bytes = new byte[dwSize];
|
|
||||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
|
||||||
Marshal.FreeHGlobal(unmanagedPointer);
|
|
||||||
|
|
||||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
|
||||||
io.AddInputCharactersUTF8(lpstr);
|
|
||||||
|
|
||||||
this.ImmComp = string.Empty;
|
|
||||||
this.ImmCandNative = default;
|
|
||||||
this.ImmCand.Clear();
|
|
||||||
this.ToggleWindow(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the position of the cursor.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The position of the cursor.</returns>
|
|
||||||
internal Vector2 GetCursorPos()
|
|
||||||
{
|
|
||||||
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void LoadCand(IntPtr hWnd)
|
|
||||||
{
|
|
||||||
if (hWnd == IntPtr.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var hImc = ImmGetContext(hWnd);
|
|
||||||
if (hImc == IntPtr.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var size = ImmGetCandidateListW(hImc, 0, IntPtr.Zero, 0);
|
|
||||||
if (size == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
|
||||||
size = ImmGetCandidateListW(hImc, 0, candlistPtr, (uint)size);
|
|
||||||
|
|
||||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
|
||||||
var pageSize = candlist.PageSize;
|
|
||||||
var candCount = candlist.Count;
|
|
||||||
|
|
||||||
if (pageSize > 0 && candCount > 1)
|
|
||||||
{
|
|
||||||
var dwOffsets = new int[candCount];
|
|
||||||
for (var i = 0; i < candCount; i++)
|
|
||||||
{
|
|
||||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var pageStart = candlist.PageStart;
|
|
||||||
|
|
||||||
var cand = new string[pageSize];
|
|
||||||
this.ImmCand.Clear();
|
|
||||||
|
|
||||||
for (var i = 0; i < pageSize; i++)
|
|
||||||
{
|
|
||||||
var offStart = dwOffsets[i + pageStart];
|
|
||||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
|
||||||
|
|
||||||
var pStrStart = candlistPtr + (int)offStart;
|
|
||||||
var pStrEnd = candlistPtr + (int)offEnd;
|
|
||||||
|
|
||||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
var candBytes = new byte[len];
|
|
||||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
|
||||||
|
|
||||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
|
||||||
cand[i] = candStr;
|
|
||||||
|
|
||||||
this.ImmCand.Add(candStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(candlistPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui.dll to become available.")]
|
|
||||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
|
||||||
var scanner = new SigScanner(module);
|
|
||||||
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
|
||||||
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
|
||||||
|
|
||||||
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
|
||||||
this.cursorPos->X = 0f;
|
|
||||||
this.cursorPos->Y = 0f;
|
|
||||||
|
|
||||||
var asm = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"push rax",
|
|
||||||
$"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}",
|
|
||||||
$"movss [rax],xmm7",
|
|
||||||
$"mov rax, {(IntPtr)this.cursorPos}",
|
|
||||||
$"movss [rax],xmm6",
|
|
||||||
$"pop rax",
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
|
||||||
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
|
||||||
this.imguiTextInputCursorHook?.Enable();
|
|
||||||
|
|
||||||
this.IsEnabled = true;
|
|
||||||
Log.Information("Enabled!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Information(ex, "Enable failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ToggleWindow(bool visible)
|
|
||||||
{
|
|
||||||
if (visible)
|
|
||||||
Service<DalamudInterface>.GetNullable()?.OpenImeWindow();
|
|
||||||
else
|
|
||||||
Service<DalamudInterface>.GetNullable()?.CloseImeWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -611,29 +611,51 @@ public enum SeIconChar
|
||||||
QuestRepeatable = 0xE0BF,
|
QuestRepeatable = 0xE0BF,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IME hiragana icon unicode character.
|
/// The [あ] character indicating that the Japanese IME is in full-width Hiragana input mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Half-width Hiragana exists as a Windows API constant, but the feature is unused, or at least unexposed to the end user via the IME.
|
||||||
|
/// </remarks>
|
||||||
ImeHiragana = 0xE020,
|
ImeHiragana = 0xE020,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IME katakana icon unicode character.
|
/// The [ア] character indicating that the Japanese IME is in full-width Katakana input mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ImeKatakana = 0xE021,
|
ImeKatakana = 0xE021,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IME alphanumeric icon unicode character.
|
/// The [A] character indicating that Japanese or Korean IME is in full-width Latin character input mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ImeAlphanumeric = 0xE022,
|
ImeAlphanumeric = 0xE022,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IME katakana half-width icon unicode character.
|
/// The [_ア] character indicating that the Japanese IME is in half-width Katakana input mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ImeKatakanaHalfWidth = 0xE023,
|
ImeKatakanaHalfWidth = 0xE023,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IME alphanumeric half-width icon unicode character.
|
/// The [_A] character indicating that Japanese or Korean IME is in half-width Latin character input mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ImeAlphanumericHalfWidth = 0xE024,
|
ImeAlphanumericHalfWidth = 0xE024,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The [가] character indicating that the Korean IME is in Hangul input mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use <see cref="ImeAlphanumeric"/> and <see cref="ImeAlphanumericHalfWidth"/> for alphanumeric input mode,
|
||||||
|
/// toggled via Alt+=.
|
||||||
|
/// </remarks>
|
||||||
|
ImeKoreanHangul = 0xE025,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The [中] character indicating that the Chinese IME is in Han character input mode.
|
||||||
|
/// </summary>
|
||||||
|
ImeChineseHan = 0xE026,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The [英] character indicating that the Chinese IME is in Latin character input mode.
|
||||||
|
/// </summary>
|
||||||
|
ImeChineseLatin = 0xE027,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The instance (1) icon unicode character.
|
/// The instance (1) icon unicode character.
|
||||||
|
|
|
||||||
144
Dalamud/Hooking/WndProcHook/WndProcEventArgs.cs
Normal file
144
Dalamud/Hooking/WndProcHook/WndProcEventArgs.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.WndProcHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event arguments for <see cref="WndProcEventDelegate"/>,
|
||||||
|
/// and the manager for individual WndProc hook.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed unsafe class WndProcEventArgs
|
||||||
|
{
|
||||||
|
private readonly WndProcHookManager owner;
|
||||||
|
private readonly delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT> oldWndProcW;
|
||||||
|
private readonly WndProcDelegate myWndProc;
|
||||||
|
|
||||||
|
private GCHandle gcHandle;
|
||||||
|
private bool released;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="WndProcEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">The owner.</param>
|
||||||
|
/// <param name="hwnd">The handle of the target window of the message.</param>
|
||||||
|
/// <param name="viewportId">The viewport ID.</param>
|
||||||
|
internal WndProcEventArgs(WndProcHookManager owner, HWND hwnd, int viewportId)
|
||||||
|
{
|
||||||
|
this.Hwnd = hwnd;
|
||||||
|
this.owner = owner;
|
||||||
|
this.ViewportId = viewportId;
|
||||||
|
this.myWndProc = this.WndProcDetour;
|
||||||
|
this.oldWndProcW = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)SetWindowLongPtrW(
|
||||||
|
hwnd,
|
||||||
|
GWLP.GWLP_WNDPROC,
|
||||||
|
Marshal.GetFunctionPointerForDelegate(this.myWndProc));
|
||||||
|
this.gcHandle = GCHandle.Alloc(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
private delegate LRESULT WndProcDelegate(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the handle of the target window of the message.
|
||||||
|
/// </summary>
|
||||||
|
public HWND Hwnd { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ImGui viewport ID.
|
||||||
|
/// </summary>
|
||||||
|
public int ViewportId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the message.
|
||||||
|
/// </summary>
|
||||||
|
public uint Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the WPARAM.
|
||||||
|
/// </summary>
|
||||||
|
public WPARAM WParam { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LPARAM.
|
||||||
|
/// </summary>
|
||||||
|
public LPARAM LParam { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to suppress calling the next WndProc in the chain.<br />
|
||||||
|
/// Does nothing if changed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool SuppressCall { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the return value.<br />
|
||||||
|
/// Has the return value from next window procedure, if accessed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||||
|
/// </summary>
|
||||||
|
public LRESULT ReturnValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets <see cref="SuppressCall"/> to <c>true</c> and sets <see cref="ReturnValue"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="returnValue">The new return value.</param>
|
||||||
|
public void SuppressWithValue(LRESULT returnValue)
|
||||||
|
{
|
||||||
|
this.ReturnValue = returnValue;
|
||||||
|
this.SuppressCall = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets <see cref="SuppressCall"/> to <c>true</c> and sets <see cref="ReturnValue"/> from the result of
|
||||||
|
/// <see cref="DefWindowProcW"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void SuppressWithDefault()
|
||||||
|
{
|
||||||
|
this.ReturnValue = DefWindowProcW(this.Hwnd, this.Message, this.WParam, this.LParam);
|
||||||
|
this.SuppressCall = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDisposable.Dispose"/>
|
||||||
|
internal void InternalRelease()
|
||||||
|
{
|
||||||
|
if (this.released)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.released = true;
|
||||||
|
SendMessageW(this.Hwnd, WM.WM_NULL, 0, 0);
|
||||||
|
this.FinalRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinalRelease()
|
||||||
|
{
|
||||||
|
if (!this.gcHandle.IsAllocated)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.gcHandle.Free();
|
||||||
|
SetWindowLongPtrW(this.Hwnd, GWLP.GWLP_WNDPROC, (nint)this.oldWndProcW);
|
||||||
|
this.owner.OnHookedWindowRemoved(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LRESULT WndProcDetour(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
if (hwnd != this.Hwnd)
|
||||||
|
return CallWindowProcW(this.oldWndProcW, hwnd, uMsg, wParam, lParam);
|
||||||
|
|
||||||
|
this.SuppressCall = false;
|
||||||
|
this.ReturnValue = 0;
|
||||||
|
this.Message = uMsg;
|
||||||
|
this.WParam = wParam;
|
||||||
|
this.LParam = lParam;
|
||||||
|
this.owner.InvokePreWndProc(this);
|
||||||
|
|
||||||
|
if (!this.SuppressCall)
|
||||||
|
this.ReturnValue = CallWindowProcW(this.oldWndProcW, hwnd, uMsg, wParam, lParam);
|
||||||
|
|
||||||
|
this.owner.InvokePostWndProc(this);
|
||||||
|
|
||||||
|
if (uMsg == WM.WM_NCDESTROY || this.released)
|
||||||
|
this.FinalRelease();
|
||||||
|
|
||||||
|
return this.ReturnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Dalamud/Hooking/WndProcHook/WndProcEventDelegate.cs
Normal file
7
Dalamud/Hooking/WndProcHook/WndProcEventDelegate.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Dalamud.Hooking.WndProcHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for overriding WndProc.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
internal delegate void WndProcEventDelegate(WndProcEventArgs args);
|
||||||
136
Dalamud/Hooking/WndProcHook/WndProcHookManager.cs
Normal file
136
Dalamud/Hooking/WndProcHook/WndProcHookManager.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.WndProcHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages WndProc hooks for game main window and extra ImGui viewport windows.
|
||||||
|
/// </summary>
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
internal sealed class WndProcHookManager : IServiceType, IDisposable
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new(nameof(WndProcHookManager));
|
||||||
|
|
||||||
|
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||||
|
private readonly Dictionary<HWND, WndProcEventArgs> wndProcOverrides = new();
|
||||||
|
|
||||||
|
private HWND mainWindowHwnd;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private unsafe WndProcHookManager()
|
||||||
|
{
|
||||||
|
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
||||||
|
null,
|
||||||
|
"user32.dll",
|
||||||
|
"DispatchMessageW",
|
||||||
|
0,
|
||||||
|
this.DispatchMessageWDetour);
|
||||||
|
this.dispatchMessageWHook.Enable();
|
||||||
|
|
||||||
|
// Capture the game main window handle,
|
||||||
|
// so that no guarantees would have to be made on the service dispose order.
|
||||||
|
Service<InterfaceManager.InterfaceManagerWithScene>
|
||||||
|
.GetAsync()
|
||||||
|
.ContinueWith(r => this.mainWindowHwnd = (HWND)r.Result.Manager.GameWindowHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
private unsafe delegate nint DispatchMessageWDelegate(MSG* msg);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called before WndProc.
|
||||||
|
/// </summary>
|
||||||
|
public event WndProcEventDelegate? PreWndProc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called after WndProc.
|
||||||
|
/// </summary>
|
||||||
|
public event WndProcEventDelegate? PostWndProc;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.dispatchMessageWHook.IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.dispatchMessageWHook.Dispose();
|
||||||
|
|
||||||
|
// Ensure that either we're on the main thread, or DispatchMessage is executed at least once.
|
||||||
|
// The game calls DispatchMessageW only from its main thread, so if we're already on one,
|
||||||
|
// this line does nothing; if not, it will require a cycle of GetMessage ... DispatchMessageW,
|
||||||
|
// which at the point of returning from DispatchMessageW(=point of returning from SendMessageW),
|
||||||
|
// the hook would be guaranteed to be fully disabled and detour delegate would be safe to be released.
|
||||||
|
SendMessageW(this.mainWindowHwnd, WM.WM_NULL, 0, 0);
|
||||||
|
|
||||||
|
// Now this.wndProcOverrides cannot be touched from other thread.
|
||||||
|
foreach (var v in this.wndProcOverrides.Values)
|
||||||
|
v.InternalRelease();
|
||||||
|
this.wndProcOverrides.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes <see cref="PreWndProc"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
internal void InvokePreWndProc(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.PreWndProc?.Invoke(args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"{nameof(this.PreWndProc)} error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes <see cref="PostWndProc"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
internal void InvokePostWndProc(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.PostWndProc?.Invoke(args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"{nameof(this.PostWndProc)} error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes <paramref name="args"/> from the list of known WndProc overrides.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Object to remove.</param>
|
||||||
|
internal void OnHookedWindowRemoved(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
if (!this.dispatchMessageWHook.IsDisposed)
|
||||||
|
this.wndProcOverrides.Remove(args.Hwnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detour for <see cref="DispatchMessageW"/>. Used to discover new windows to hook.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message.</param>
|
||||||
|
/// <returns>The original return value.</returns>
|
||||||
|
private unsafe nint DispatchMessageWDetour(MSG* msg)
|
||||||
|
{
|
||||||
|
if (!this.wndProcOverrides.ContainsKey(msg->hwnd)
|
||||||
|
&& ImGuiHelpers.FindViewportId(msg->hwnd) is var vpid and >= 0)
|
||||||
|
{
|
||||||
|
this.wndProcOverrides[msg->hwnd] = new(this, msg->hwnd, vpid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dispatchMessageWHook.Original(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
708
Dalamud/Interface/Internal/DalamudIme.cs
Normal file
708
Dalamud/Interface/Internal/DalamudIme.cs
Normal file
|
|
@ -0,0 +1,708 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Unicode;
|
||||||
|
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Hooking.WndProcHook;
|
||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class handles CJK IME.
|
||||||
|
/// </summary>
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
||||||
|
{
|
||||||
|
private const int ImGuiContextTextStateOffset = 0x4588;
|
||||||
|
private const int CImGuiStbTextCreateUndoOffset = 0xB57A0;
|
||||||
|
private const int CImGuiStbTextUndoOffset = 0xB59C0;
|
||||||
|
|
||||||
|
private static readonly UnicodeRange[] HanRange =
|
||||||
|
{
|
||||||
|
UnicodeRanges.CjkRadicalsSupplement,
|
||||||
|
UnicodeRanges.CjkSymbolsandPunctuation,
|
||||||
|
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
|
||||||
|
UnicodeRanges.CjkUnifiedIdeographs,
|
||||||
|
UnicodeRanges.CjkCompatibilityIdeographs,
|
||||||
|
UnicodeRanges.CjkCompatibilityForms,
|
||||||
|
// No more; Extension B~ are outside BMP range
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly UnicodeRange[] HangulRange =
|
||||||
|
{
|
||||||
|
UnicodeRanges.HangulJamo,
|
||||||
|
UnicodeRanges.HangulSyllables,
|
||||||
|
UnicodeRanges.HangulCompatibilityJamo,
|
||||||
|
UnicodeRanges.HangulJamoExtendedA,
|
||||||
|
UnicodeRanges.HangulJamoExtendedB,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly delegate* unmanaged<ImGuiInputTextState*, StbTextEditState*, int, int, int, void>
|
||||||
|
StbTextMakeUndoReplace;
|
||||||
|
|
||||||
|
private static readonly delegate* unmanaged<ImGuiInputTextState*, StbTextEditState*, void> StbTextUndo;
|
||||||
|
|
||||||
|
private readonly ImGuiSetPlatformImeDataDelegate setPlatformImeDataDelegate;
|
||||||
|
|
||||||
|
private (int Start, int End, int Cursor)? temporaryUndoSelection;
|
||||||
|
|
||||||
|
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")]
|
||||||
|
static DalamudIme()
|
||||||
|
{
|
||||||
|
nint cimgui;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = ImGui.GetCurrentContext();
|
||||||
|
|
||||||
|
cimgui = Process.GetCurrentProcess().Modules.Cast<ProcessModule>()
|
||||||
|
.First(x => x.ModuleName == "cimgui.dll")
|
||||||
|
.BaseAddress;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StbTextMakeUndoReplace =
|
||||||
|
(delegate* unmanaged<ImGuiInputTextState*, StbTextEditState*, int, int, int, void>)
|
||||||
|
(cimgui + CImGuiStbTextCreateUndoOffset);
|
||||||
|
StbTextUndo =
|
||||||
|
(delegate* unmanaged<ImGuiInputTextState*, StbTextEditState*, void>)
|
||||||
|
(cimgui + CImGuiStbTextUndoOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private DalamudIme() => this.setPlatformImeDataDelegate = this.ImGuiSetPlatformImeData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes an instance of the <see cref="DalamudIme"/> class.
|
||||||
|
/// </summary>
|
||||||
|
~DalamudIme() => this.ReleaseUnmanagedResources();
|
||||||
|
|
||||||
|
private delegate void ImGuiSetPlatformImeDataDelegate(ImGuiViewportPtr viewport, ImGuiPlatformImeDataPtr data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Han(Chinese) input has been detected.
|
||||||
|
/// </summary>
|
||||||
|
public bool EncounteredHan { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Hangul(Korean) input has been detected.
|
||||||
|
/// </summary>
|
||||||
|
public bool EncounteredHangul { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether to display the cursor in input text. This also deals with blinking.
|
||||||
|
/// </summary>
|
||||||
|
internal static bool ShowCursorInInputText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||||
|
return true;
|
||||||
|
if (!ImGui.GetIO().ConfigInputTextCursorBlink)
|
||||||
|
return true;
|
||||||
|
var textState = TextState;
|
||||||
|
if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0)
|
||||||
|
return true;
|
||||||
|
if (textState->CursorAnim <= 0)
|
||||||
|
return true;
|
||||||
|
return textState->CursorAnim % 1.2f <= 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the cursor position, in screen coordinates.
|
||||||
|
/// </summary>
|
||||||
|
internal Vector2 CursorPos { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the associated viewport.
|
||||||
|
/// </summary>
|
||||||
|
internal ImGuiViewportPtr AssociatedViewport { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the first imm candidate in relation to the full list.
|
||||||
|
/// </summary>
|
||||||
|
internal CANDIDATELIST ImmCandNative { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the imm candidates.
|
||||||
|
/// </summary>
|
||||||
|
internal List<string> ImmCand { get; private set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the selected imm component.
|
||||||
|
/// </summary>
|
||||||
|
internal string ImmComp { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the partial conversion from-range.
|
||||||
|
/// </summary>
|
||||||
|
internal int PartialConversionFrom { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the partial conversion to-range.
|
||||||
|
/// </summary>
|
||||||
|
internal int PartialConversionTo { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the cursor offset in the composition string.
|
||||||
|
/// </summary>
|
||||||
|
internal int CompositionCursorOffset { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether to display partial conversion status.
|
||||||
|
/// </summary>
|
||||||
|
internal bool ShowPartialConversion => this.PartialConversionFrom != 0 ||
|
||||||
|
this.PartialConversionTo != this.ImmComp.Length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the input mode icon from <see cref="SeIconChar"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal char InputModeIcon { get; private set; }
|
||||||
|
|
||||||
|
private static ImGuiInputTextState* TextState =>
|
||||||
|
(ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextTextStateOffset);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.ReleaseUnmanagedResources();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks for the characters inside <paramref name="str"/> and enables fonts accordingly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str">The string.</param>
|
||||||
|
public void ReflectCharacterEncounters(string str)
|
||||||
|
{
|
||||||
|
foreach (var chr in str)
|
||||||
|
{
|
||||||
|
if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
|
||||||
|
{
|
||||||
|
if (Service<GameFontManager>.Get()
|
||||||
|
.GetFdtReader(GameFontFamilyAndSize.Axis12)
|
||||||
|
?.FindGlyph(chr) is null)
|
||||||
|
{
|
||||||
|
if (!this.EncounteredHan)
|
||||||
|
{
|
||||||
|
this.EncounteredHan = true;
|
||||||
|
Service<InterfaceManager>.Get().RebuildFonts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HangulRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length))
|
||||||
|
{
|
||||||
|
if (!this.EncounteredHangul)
|
||||||
|
{
|
||||||
|
this.EncounteredHangul = true;
|
||||||
|
Service<InterfaceManager>.Get().RebuildFonts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes window messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
public void ProcessImeMessage(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Are we not the target of text input?
|
||||||
|
if (!ImGui.GetIO().WantTextInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var hImc = ImmGetContext(args.Hwnd);
|
||||||
|
if (hImc == nint.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var invalidTarget = TextState->Id == 0 || (TextState->Flags & ImGuiInputTextFlags.ReadOnly) != 0;
|
||||||
|
|
||||||
|
switch (args.Message)
|
||||||
|
{
|
||||||
|
case WM.WM_IME_NOTIFY
|
||||||
|
when (nint)args.WParam is IMN.IMN_OPENCANDIDATE or IMN.IMN_CLOSECANDIDATE
|
||||||
|
or IMN.IMN_CHANGECANDIDATE:
|
||||||
|
this.UpdateImeWindowStatus(hImc);
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_STARTCOMPOSITION:
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_COMPOSITION:
|
||||||
|
if (invalidTarget)
|
||||||
|
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||||
|
else
|
||||||
|
this.ReplaceCompositionString(hImc, (uint)args.LParam);
|
||||||
|
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.ImmComp}");
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_ENDCOMPOSITION:
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_CONTROL:
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_REQUEST:
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
|
||||||
|
args.SuppressWithValue(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_SETCONTEXT:
|
||||||
|
// Hide candidate and composition windows.
|
||||||
|
args.LParam = (LPARAM)((nint)args.LParam & ~(ISC_SHOWUICOMPOSITIONWINDOW | 0xF));
|
||||||
|
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}");
|
||||||
|
args.SuppressWithDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_IME_NOTIFY:
|
||||||
|
// Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.ImmComp}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_KEYDOWN when (int)args.WParam is
|
||||||
|
VK.VK_TAB
|
||||||
|
or VK.VK_PRIOR
|
||||||
|
or VK.VK_NEXT
|
||||||
|
or VK.VK_END
|
||||||
|
or VK.VK_HOME
|
||||||
|
or VK.VK_LEFT
|
||||||
|
or VK.VK_UP
|
||||||
|
or VK.VK_RIGHT
|
||||||
|
or VK.VK_DOWN
|
||||||
|
or VK.VK_RETURN:
|
||||||
|
if (this.ImmCand.Count != 0)
|
||||||
|
{
|
||||||
|
this.ClearState(hImc);
|
||||||
|
args.WParam = VK.VK_PROCESSKEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM.WM_LBUTTONDOWN:
|
||||||
|
case WM.WM_RBUTTONDOWN:
|
||||||
|
case WM.WM_MBUTTONDOWN:
|
||||||
|
case WM.WM_XBUTTONDOWN:
|
||||||
|
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UpdateInputLanguage(hImc);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ImmReleaseContext(args.Hwnd, hImc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ImmGetCompositionString(HIMC hImc, uint comp)
|
||||||
|
{
|
||||||
|
var numBytes = ImmGetCompositionStringW(hImc, comp, null, 0);
|
||||||
|
if (numBytes == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var data = stackalloc char[numBytes / 2];
|
||||||
|
_ = ImmGetCompositionStringW(hImc, comp, data, (uint)numBytes);
|
||||||
|
return new(data, 0, numBytes / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseUnmanagedResources()
|
||||||
|
{
|
||||||
|
if (ImGuiHelpers.IsImGuiInitialized)
|
||||||
|
ImGui.GetIO().SetPlatformImeDataFn = nint.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInputLanguage(HIMC hImc)
|
||||||
|
{
|
||||||
|
uint conv, sent;
|
||||||
|
ImmGetConversionStatus(hImc, &conv, &sent);
|
||||||
|
var lang = GetKeyboardLayout(0);
|
||||||
|
var open = ImmGetOpenStatus(hImc) != false;
|
||||||
|
|
||||||
|
// Log.Verbose($"{nameof(this.UpdateInputLanguage)}: conv={conv:X} sent={sent:X} open={open} lang={lang:X}");
|
||||||
|
|
||||||
|
var native = (conv & 1) != 0;
|
||||||
|
var katakana = (conv & 2) != 0;
|
||||||
|
var fullwidth = (conv & 8) != 0;
|
||||||
|
switch (lang & 0x3F)
|
||||||
|
{
|
||||||
|
case LANG.LANG_KOREAN:
|
||||||
|
if (native)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeKoreanHangul;
|
||||||
|
else if (fullwidth)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric;
|
||||||
|
else
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LANG.LANG_JAPANESE:
|
||||||
|
// wtf
|
||||||
|
// see the function called from: 48 8b 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 8b d8 e9 ?? 00 00 0
|
||||||
|
if (open && native && katakana && fullwidth)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeKatakana;
|
||||||
|
else if (open && native && katakana)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeKatakanaHalfWidth;
|
||||||
|
else if (open && native)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeHiragana;
|
||||||
|
else if (open && fullwidth)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric;
|
||||||
|
else
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LANG.LANG_CHINESE:
|
||||||
|
if (native)
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeChineseHan;
|
||||||
|
else
|
||||||
|
this.InputModeIcon = (char)SeIconChar.ImeChineseLatin;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.InputModeIcon = default;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UpdateImeWindowStatus(hImc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceCompositionString(HIMC hImc, uint comp)
|
||||||
|
{
|
||||||
|
var finalCommit = (comp & GCS.GCS_RESULTSTR) != 0;
|
||||||
|
var newString = finalCommit
|
||||||
|
? ImmGetCompositionString(hImc, GCS.GCS_RESULTSTR)
|
||||||
|
: ImmGetCompositionString(hImc, GCS.GCS_COMPSTR);
|
||||||
|
|
||||||
|
this.ReflectCharacterEncounters(newString);
|
||||||
|
|
||||||
|
if (this.temporaryUndoSelection is not null)
|
||||||
|
{
|
||||||
|
TextState->Undo();
|
||||||
|
TextState->SelectionTuple = this.temporaryUndoSelection.Value;
|
||||||
|
this.temporaryUndoSelection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextState->SanitizeSelectionRange();
|
||||||
|
if (TextState->ReplaceSelectionAndPushUndo(newString))
|
||||||
|
this.temporaryUndoSelection = TextState->SelectionTuple;
|
||||||
|
|
||||||
|
// Put the cursor at the beginning, so that the candidate window appears aligned with the text.
|
||||||
|
TextState->SetSelectionRange(TextState->SelectionTuple.Start, newString.Length, 0);
|
||||||
|
|
||||||
|
if (finalCommit)
|
||||||
|
{
|
||||||
|
this.ClearState(hImc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ImmComp = newString;
|
||||||
|
this.CompositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0);
|
||||||
|
|
||||||
|
if ((comp & GCS.GCS_COMPATTR) != 0)
|
||||||
|
{
|
||||||
|
var attrLength = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, null, 0);
|
||||||
|
var attrPtr = stackalloc byte[attrLength];
|
||||||
|
var attr = new Span<byte>(attrPtr, Math.Min(this.ImmComp.Length, attrLength));
|
||||||
|
_ = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, attrPtr, (uint)attrLength);
|
||||||
|
var l = 0;
|
||||||
|
while (l < attr.Length && attr[l] is not ATTR_TARGET_CONVERTED and not ATTR_TARGET_NOTCONVERTED)
|
||||||
|
l++;
|
||||||
|
|
||||||
|
var r = l;
|
||||||
|
while (r < attr.Length && attr[r] is ATTR_TARGET_CONVERTED or ATTR_TARGET_NOTCONVERTED)
|
||||||
|
r++;
|
||||||
|
|
||||||
|
if (r == 0 || l == this.ImmComp.Length)
|
||||||
|
(l, r) = (0, this.ImmComp.Length);
|
||||||
|
|
||||||
|
(this.PartialConversionFrom, this.PartialConversionTo) = (l, r);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.PartialConversionFrom = 0;
|
||||||
|
this.PartialConversionTo = this.ImmComp.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UpdateImeWindowStatus(hImc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearState(HIMC hImc)
|
||||||
|
{
|
||||||
|
this.ImmComp = string.Empty;
|
||||||
|
this.PartialConversionFrom = this.PartialConversionTo = 0;
|
||||||
|
this.CompositionCursorOffset = 0;
|
||||||
|
this.temporaryUndoSelection = null;
|
||||||
|
TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd;
|
||||||
|
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||||
|
this.UpdateImeWindowStatus(default);
|
||||||
|
|
||||||
|
// Log.Information($"{nameof(this.ClearState)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadCand(HIMC hImc)
|
||||||
|
{
|
||||||
|
this.ImmCand.Clear();
|
||||||
|
this.ImmCandNative = default;
|
||||||
|
|
||||||
|
if (hImc == default)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var size = (int)ImmGetCandidateListW(hImc, 0, null, 0);
|
||||||
|
if (size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pStorage = stackalloc byte[size];
|
||||||
|
if (size != ImmGetCandidateListW(hImc, 0, (CANDIDATELIST*)pStorage, (uint)size))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ref var candlist = ref *(CANDIDATELIST*)pStorage;
|
||||||
|
this.ImmCandNative = candlist;
|
||||||
|
|
||||||
|
if (candlist.dwPageSize == 0 || candlist.dwCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var i in Enumerable.Range(
|
||||||
|
(int)candlist.dwPageStart,
|
||||||
|
(int)Math.Min(candlist.dwCount - candlist.dwPageStart, candlist.dwPageSize)))
|
||||||
|
{
|
||||||
|
this.ImmCand.Add(new((char*)(pStorage + candlist.dwOffset[i])));
|
||||||
|
this.ReflectCharacterEncounters(this.ImmCand[^1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateImeWindowStatus(HIMC hImc)
|
||||||
|
{
|
||||||
|
if (Service<DalamudInterface>.GetNullable() is not { } di)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.LoadCand(hImc);
|
||||||
|
if (this.ImmCand.Count != 0 || this.ShowPartialConversion || this.InputModeIcon != default)
|
||||||
|
di.OpenImeWindow();
|
||||||
|
else
|
||||||
|
di.CloseImeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImGuiSetPlatformImeData(ImGuiViewportPtr viewport, ImGuiPlatformImeDataPtr data)
|
||||||
|
{
|
||||||
|
this.CursorPos = data.InputPos;
|
||||||
|
this.AssociatedViewport = data.WantVisible ? viewport : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui context initialization.")]
|
||||||
|
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
||||||
|
{
|
||||||
|
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Expected {nameof(InterfaceManager.InterfaceManagerWithScene)} to have initialized ImGui.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ported from imstb_textedit.h.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0xE2C)]
|
||||||
|
private struct StbTextEditState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Position of the text cursor within the string.
|
||||||
|
/// </summary>
|
||||||
|
public int Cursor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selection start point.
|
||||||
|
/// </summary>
|
||||||
|
public int SelectStart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// selection start and end point in characters; if equal, no selection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that start may be less than or greater than end (e.g. when dragging the mouse,
|
||||||
|
/// start is where the initial click was, and you can drag in either direction.)
|
||||||
|
/// </remarks>
|
||||||
|
public int SelectEnd;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Each text field keeps its own insert mode state.
|
||||||
|
/// To keep an app-wide insert mode, copy this value in/out of the app state.
|
||||||
|
/// </summary>
|
||||||
|
public byte InsertMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Page size in number of row.
|
||||||
|
/// This value MUST be set to >0 for pageup or pagedown in multilines documents.
|
||||||
|
/// </summary>
|
||||||
|
public int RowCountPerPage;
|
||||||
|
|
||||||
|
// Remainder is stb-private data.
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct ImGuiInputTextState
|
||||||
|
{
|
||||||
|
public uint Id;
|
||||||
|
public int CurLenW;
|
||||||
|
public int CurLenA;
|
||||||
|
public ImVector<char> TextWRaw;
|
||||||
|
public ImVector<byte> TextARaw;
|
||||||
|
public ImVector<byte> InitialTextARaw;
|
||||||
|
public bool TextAIsValid;
|
||||||
|
public int BufCapacityA;
|
||||||
|
public float ScrollX;
|
||||||
|
public StbTextEditState Stb;
|
||||||
|
public float CursorAnim;
|
||||||
|
public bool CursorFollow;
|
||||||
|
public bool SelectedAllMouseLock;
|
||||||
|
public bool Edited;
|
||||||
|
public ImGuiInputTextFlags Flags;
|
||||||
|
|
||||||
|
public ImVectorWrapper<char> TextW => new((ImVector*)&this.ThisPtr->TextWRaw);
|
||||||
|
|
||||||
|
public (int Start, int End, int Cursor) SelectionTuple
|
||||||
|
{
|
||||||
|
get => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor);
|
||||||
|
set => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImGuiInputTextState* ThisPtr => (ImGuiInputTextState*)Unsafe.AsPointer(ref this);
|
||||||
|
|
||||||
|
public void SetSelectionRange(int offset, int length, int relativeCursorOffset)
|
||||||
|
{
|
||||||
|
this.Stb.SelectStart = offset;
|
||||||
|
this.Stb.SelectEnd = offset + length;
|
||||||
|
if (relativeCursorOffset >= 0)
|
||||||
|
this.Stb.Cursor = this.Stb.SelectStart + relativeCursorOffset;
|
||||||
|
else
|
||||||
|
this.Stb.Cursor = this.Stb.SelectEnd + 1 + relativeCursorOffset;
|
||||||
|
this.SanitizeSelectionRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SanitizeSelectionRange()
|
||||||
|
{
|
||||||
|
ref var s = ref this.Stb.SelectStart;
|
||||||
|
ref var e = ref this.Stb.SelectEnd;
|
||||||
|
ref var c = ref this.Stb.Cursor;
|
||||||
|
s = Math.Clamp(s, 0, this.CurLenW);
|
||||||
|
e = Math.Clamp(e, 0, this.CurLenW);
|
||||||
|
c = Math.Clamp(c, 0, this.CurLenW);
|
||||||
|
if (s == e)
|
||||||
|
s = e = c;
|
||||||
|
if (s > e)
|
||||||
|
(s, e) = (e, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Undo() => StbTextUndo(this.ThisPtr, &this.ThisPtr->Stb);
|
||||||
|
|
||||||
|
public bool MakeUndoReplace(int offset, int oldLength, int newLength)
|
||||||
|
{
|
||||||
|
if (oldLength == 0 && newLength == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
StbTextMakeUndoReplace(this.ThisPtr, &this.ThisPtr->Stb, offset, oldLength, newLength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ReplaceSelectionAndPushUndo(ReadOnlySpan<char> newText)
|
||||||
|
{
|
||||||
|
var off = this.Stb.SelectStart;
|
||||||
|
var len = this.Stb.SelectEnd - this.Stb.SelectStart;
|
||||||
|
return this.MakeUndoReplace(off, len, newText.Length) && this.ReplaceChars(off, len, newText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ReplaceChars(int pos, int len, ReadOnlySpan<char> newText)
|
||||||
|
{
|
||||||
|
this.DeleteChars(pos, len);
|
||||||
|
return this.InsertChars(pos, newText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See imgui_widgets.cpp: STB_TEXTEDIT_DELETECHARS
|
||||||
|
public void DeleteChars(int pos, int n)
|
||||||
|
{
|
||||||
|
if (n == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dst = this.TextW.Data + pos;
|
||||||
|
|
||||||
|
// We maintain our buffer length in both UTF-8 and wchar formats
|
||||||
|
this.Edited = true;
|
||||||
|
this.CurLenA -= Encoding.UTF8.GetByteCount(dst, n);
|
||||||
|
this.CurLenW -= n;
|
||||||
|
|
||||||
|
// Offset remaining text (FIXME-OPT: Use memmove)
|
||||||
|
var src = this.TextW.Data + pos + n;
|
||||||
|
int i;
|
||||||
|
for (i = 0; src[i] != 0; i++)
|
||||||
|
dst[i] = src[i];
|
||||||
|
dst[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// See imgui_widgets.cpp: STB_TEXTEDIT_INSERTCHARS
|
||||||
|
public bool InsertChars(int pos, ReadOnlySpan<char> newText)
|
||||||
|
{
|
||||||
|
if (newText.Length == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var isResizable = (this.Flags & ImGuiInputTextFlags.CallbackResize) != 0;
|
||||||
|
var textLen = this.CurLenW;
|
||||||
|
Debug.Assert(pos <= textLen, "pos <= text_len");
|
||||||
|
|
||||||
|
var newTextLenUtf8 = Encoding.UTF8.GetByteCount(newText);
|
||||||
|
if (!isResizable && newTextLenUtf8 + this.CurLenA + 1 > this.BufCapacityA)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Grow internal buffer if needed
|
||||||
|
if (newText.Length + textLen + 1 > this.TextW.Length)
|
||||||
|
{
|
||||||
|
if (!isResizable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Debug.Assert(textLen < this.TextW.Length, "text_len < this.TextW.Length");
|
||||||
|
this.TextW.Resize(textLen + Math.Clamp(newText.Length * 4, 32, Math.Max(256, newText.Length)) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = this.TextW.DataSpan;
|
||||||
|
if (pos != textLen)
|
||||||
|
text.Slice(pos, textLen - pos).CopyTo(text[(pos + newText.Length)..]);
|
||||||
|
newText.CopyTo(text[pos..]);
|
||||||
|
|
||||||
|
this.Edited = true;
|
||||||
|
this.CurLenW += newText.Length;
|
||||||
|
this.CurLenA += newTextLenUtf8;
|
||||||
|
this.TextW[this.CurLenW] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
private readonly ComponentDemoWindow componentDemoWindow;
|
private readonly ComponentDemoWindow componentDemoWindow;
|
||||||
private readonly DataWindow dataWindow;
|
private readonly DataWindow dataWindow;
|
||||||
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
||||||
private readonly ImeWindow imeWindow;
|
private readonly DalamudImeWindow imeWindow;
|
||||||
private readonly ConsoleWindow consoleWindow;
|
private readonly ConsoleWindow consoleWindow;
|
||||||
private readonly PluginStatWindow pluginStatWindow;
|
private readonly PluginStatWindow pluginStatWindow;
|
||||||
private readonly PluginInstallerWindow pluginWindow;
|
private readonly PluginInstallerWindow pluginWindow;
|
||||||
|
|
@ -111,7 +111,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
|
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
|
||||||
this.dataWindow = new DataWindow() { IsOpen = false };
|
this.dataWindow = new DataWindow() { IsOpen = false };
|
||||||
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
|
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
|
||||||
this.imeWindow = new ImeWindow() { IsOpen = false };
|
this.imeWindow = new DalamudImeWindow() { IsOpen = false };
|
||||||
this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
|
this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
|
||||||
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
||||||
this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
|
this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
|
||||||
|
|
@ -256,7 +256,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true;
|
public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the <see cref="ImeWindow"/>.
|
/// Opens the <see cref="DalamudImeWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OpenImeWindow() => this.imeWindow.IsOpen = true;
|
public void OpenImeWindow() => this.imeWindow.IsOpen = true;
|
||||||
|
|
||||||
|
|
@ -356,7 +356,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
#region Close
|
#region Close
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Closes the <see cref="ImeWindow"/>.
|
/// Closes the <see cref="DalamudImeWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseImeWindow() => this.imeWindow.IsOpen = false;
|
public void CloseImeWindow() => this.imeWindow.IsOpen = false;
|
||||||
|
|
||||||
|
|
@ -408,7 +408,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle();
|
public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the <see cref="ImeWindow"/>.
|
/// Toggles the <see cref="DalamudImeWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ToggleImeWindow() => this.imeWindow.Toggle();
|
public void ToggleImeWindow() => this.imeWindow.Toggle();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis
|
||||||
ptr[str.Length] = default;
|
ptr[str.Length] = default;
|
||||||
GlobalUnlock(hMem);
|
GlobalUnlock(hMem);
|
||||||
|
|
||||||
|
EmptyClipboard();
|
||||||
SetClipboardData(CF.CF_UNICODETEXT, hMem);
|
SetClipboardData(CF.CF_UNICODETEXT, hMem);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -158,9 +159,9 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis
|
||||||
return this.clipboardData.Data;
|
return this.clipboardData.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hMem = (HGLOBAL)GetClipboardData(CF.CF_UNICODETEXT);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hMem = (HGLOBAL)GetClipboardData(CF.CF_UNICODETEXT);
|
|
||||||
if (hMem != default)
|
if (hMem != default)
|
||||||
{
|
{
|
||||||
var ptr = (char*)GlobalLock(hMem);
|
var ptr = (char*)GlobalLock(hMem);
|
||||||
|
|
@ -191,6 +192,8 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
if (hMem != default)
|
||||||
|
GlobalUnlock(hMem);
|
||||||
CloseClipboard();
|
CloseClipboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,16 @@ using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Unicode;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.GamePad;
|
using Dalamud.Game.ClientState.GamePad;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Gui.Internal;
|
|
||||||
using Dalamud.Game.Internal.DXGI;
|
using Dalamud.Game.Internal.DXGI;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Hooking.WndProcHook;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
|
@ -73,12 +74,16 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudIme dalamudIme = Service<DalamudIme>.Get();
|
||||||
|
|
||||||
private readonly ManualResetEvent fontBuildSignal;
|
private readonly ManualResetEvent fontBuildSignal;
|
||||||
private readonly SwapChainVtableResolver address;
|
private readonly SwapChainVtableResolver address;
|
||||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
|
||||||
private readonly Hook<SetCursorDelegate> setCursorHook;
|
private readonly Hook<SetCursorDelegate> setCursorHook;
|
||||||
private Hook<ProcessMessageDelegate> processMessageHook;
|
|
||||||
private RawDX11Scene? scene;
|
private RawDX11Scene? scene;
|
||||||
|
|
||||||
private Hook<PresentDelegate>? presentHook;
|
private Hook<PresentDelegate>? presentHook;
|
||||||
|
|
@ -92,8 +97,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private InterfaceManager()
|
private InterfaceManager()
|
||||||
{
|
{
|
||||||
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
|
||||||
null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
|
|
||||||
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
||||||
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||||
|
|
||||||
|
|
@ -111,12 +114,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
||||||
private delegate IntPtr DispatchMessageWDelegate(ref User32.MSG msg);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr ProcessMessageDelegate(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This event gets called each frame to facilitate ImGui drawing.
|
/// This event gets called each frame to facilitate ImGui drawing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -236,10 +233,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
this.setCursorHook.Dispose();
|
this.setCursorHook.Dispose();
|
||||||
this.presentHook?.Dispose();
|
this.presentHook?.Dispose();
|
||||||
this.resizeBuffersHook?.Dispose();
|
this.resizeBuffersHook?.Dispose();
|
||||||
this.dispatchMessageWHook.Dispose();
|
|
||||||
this.processMessageHook?.Dispose();
|
|
||||||
}).Wait();
|
}).Wait();
|
||||||
|
|
||||||
|
this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
|
||||||
this.scene?.Dispose();
|
this.scene?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -660,6 +656,17 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
|
|
||||||
this.scene = newScene;
|
this.scene = newScene;
|
||||||
Service<InterfaceManagerWithScene>.Provide(new(this));
|
Service<InterfaceManagerWithScene>.Provide(new(this));
|
||||||
|
|
||||||
|
this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam);
|
||||||
|
if (r is not null)
|
||||||
|
args.SuppressWithValue(r.Value);
|
||||||
|
|
||||||
|
this.dalamudIme.ProcessImeMessage(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -778,10 +785,22 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
var fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKkr-Regular.otf");
|
var fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKkr-Regular.otf");
|
||||||
if (!File.Exists(fontPathKr))
|
if (!File.Exists(fontPathKr))
|
||||||
fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansKR-Regular.otf");
|
fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansKR-Regular.otf");
|
||||||
|
if (!File.Exists(fontPathKr))
|
||||||
|
fontPathKr = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "malgun.ttf");
|
||||||
if (!File.Exists(fontPathKr))
|
if (!File.Exists(fontPathKr))
|
||||||
fontPathKr = null;
|
fontPathKr = null;
|
||||||
Log.Verbose("[FONT] fontPathKr = {0}", fontPathKr);
|
Log.Verbose("[FONT] fontPathKr = {0}", fontPathKr);
|
||||||
|
|
||||||
|
var fontPathChs = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "msyh.ttc");
|
||||||
|
if (!File.Exists(fontPathChs))
|
||||||
|
fontPathChs = null;
|
||||||
|
Log.Verbose("[FONT] fontPathChs = {0}", fontPathChs);
|
||||||
|
|
||||||
|
var fontPathCht = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts", "msjh.ttc");
|
||||||
|
if (!File.Exists(fontPathCht))
|
||||||
|
fontPathCht = null;
|
||||||
|
Log.Verbose("[FONT] fontPathChs = {0}", fontPathCht);
|
||||||
|
|
||||||
// Default font
|
// Default font
|
||||||
Log.Verbose("[FONT] SetupFonts - Default font");
|
Log.Verbose("[FONT] SetupFonts - Default font");
|
||||||
var fontInfo = new TargetFontModification(
|
var fontInfo = new TargetFontModification(
|
||||||
|
|
@ -809,7 +828,8 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
this.loadedFontInfo[DefaultFont] = fontInfo;
|
this.loadedFontInfo[DefaultFont] = fontInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fontPathKr != null && Service<DalamudConfiguration>.Get().EffectiveLanguage == "ko")
|
if (fontPathKr != null
|
||||||
|
&& (Service<DalamudConfiguration>.Get().EffectiveLanguage == "ko" || this.dalamudIme.EncounteredHangul))
|
||||||
{
|
{
|
||||||
fontConfig.MergeMode = true;
|
fontConfig.MergeMode = true;
|
||||||
fontConfig.GlyphRanges = ioFonts.GetGlyphRangesKorean();
|
fontConfig.GlyphRanges = ioFonts.GetGlyphRangesKorean();
|
||||||
|
|
@ -818,6 +838,46 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
fontConfig.MergeMode = false;
|
fontConfig.MergeMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fontPathCht != null && Service<DalamudConfiguration>.Get().EffectiveLanguage == "tw")
|
||||||
|
{
|
||||||
|
fontConfig.MergeMode = true;
|
||||||
|
var rangeHandle = GCHandle.Alloc(new ushort[]
|
||||||
|
{
|
||||||
|
(ushort)UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint,
|
||||||
|
(ushort)(UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint +
|
||||||
|
(UnicodeRanges.CjkUnifiedIdeographs.Length - 1)),
|
||||||
|
(ushort)UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint,
|
||||||
|
(ushort)(UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint +
|
||||||
|
(UnicodeRanges.CjkUnifiedIdeographsExtensionA.Length - 1)),
|
||||||
|
0,
|
||||||
|
}, GCHandleType.Pinned);
|
||||||
|
garbageList.Add(rangeHandle);
|
||||||
|
fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
|
||||||
|
fontConfig.PixelSnapH = true;
|
||||||
|
ioFonts.AddFontFromFileTTF(fontPathCht, fontConfig.SizePixels, fontConfig);
|
||||||
|
fontConfig.MergeMode = false;
|
||||||
|
}
|
||||||
|
else if (fontPathChs != null && (Service<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|
||||||
|
|| this.dalamudIme.EncounteredHan))
|
||||||
|
{
|
||||||
|
fontConfig.MergeMode = true;
|
||||||
|
var rangeHandle = GCHandle.Alloc(new ushort[]
|
||||||
|
{
|
||||||
|
(ushort)UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint,
|
||||||
|
(ushort)(UnicodeRanges.CjkUnifiedIdeographs.FirstCodePoint +
|
||||||
|
(UnicodeRanges.CjkUnifiedIdeographs.Length - 1)),
|
||||||
|
(ushort)UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint,
|
||||||
|
(ushort)(UnicodeRanges.CjkUnifiedIdeographsExtensionA.FirstCodePoint +
|
||||||
|
(UnicodeRanges.CjkUnifiedIdeographsExtensionA.Length - 1)),
|
||||||
|
0,
|
||||||
|
}, GCHandleType.Pinned);
|
||||||
|
garbageList.Add(rangeHandle);
|
||||||
|
fontConfig.GlyphRanges = rangeHandle.AddrOfPinnedObject();
|
||||||
|
fontConfig.PixelSnapH = true;
|
||||||
|
ioFonts.AddFontFromFileTTF(fontPathChs, fontConfig.SizePixels, fontConfig);
|
||||||
|
fontConfig.MergeMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
// FontAwesome icon font
|
// FontAwesome icon font
|
||||||
Log.Verbose("[FONT] SetupFonts - FontAwesome icon font");
|
Log.Verbose("[FONT] SetupFonts - FontAwesome icon font");
|
||||||
{
|
{
|
||||||
|
|
@ -1095,15 +1155,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
||||||
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
|
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
|
||||||
|
|
||||||
var wndProcAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8");
|
|
||||||
Log.Verbose($"WndProc address 0x{wndProcAddress.ToInt64():X}");
|
|
||||||
this.processMessageHook = Hook<ProcessMessageDelegate>.FromAddress(wndProcAddress, this.ProcessMessageDetour);
|
|
||||||
|
|
||||||
this.setCursorHook.Enable();
|
this.setCursorHook.Enable();
|
||||||
this.presentHook.Enable();
|
this.presentHook.Enable();
|
||||||
this.resizeBuffersHook.Enable();
|
this.resizeBuffersHook.Enable();
|
||||||
this.dispatchMessageWHook.Enable();
|
|
||||||
this.processMessageHook.Enable();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1124,25 +1178,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
this.isRebuildingFonts = false;
|
this.isRebuildingFonts = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe IntPtr ProcessMessageDetour(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled)
|
|
||||||
{
|
|
||||||
var ime = Service<DalamudIME>.GetNullable();
|
|
||||||
var res = ime?.ProcessWndProcW(hWnd, (User32.WindowMessage)msg, (void*)wParam, (void*)lParam);
|
|
||||||
return this.processMessageHook.Original(hWnd, msg, wParam, lParam, handeled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IntPtr DispatchMessageWDetour(ref User32.MSG msg)
|
|
||||||
{
|
|
||||||
if (msg.hwnd == this.GameWindowHandle && this.scene != null)
|
|
||||||
{
|
|
||||||
var res = this.scene.ProcessWndProcW(msg.hwnd, msg.message, (void*)msg.wParam, (void*)msg.lParam);
|
|
||||||
if (res != null)
|
|
||||||
return res.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.dispatchMessageWHook.IsDisposed ? User32.DispatchMessage(ref msg) : this.dispatchMessageWHook.Original(ref msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
@ -273,7 +273,10 @@ internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITe
|
||||||
this.fallbackTextureWrap?.Dispose();
|
this.fallbackTextureWrap?.Dispose();
|
||||||
this.framework.Update -= this.FrameworkOnUpdate;
|
this.framework.Update -= this.FrameworkOnUpdate;
|
||||||
|
|
||||||
Log.Verbose("Disposing {Num} left behind textures.");
|
if (this.activeTextures.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Verbose("Disposing {Num} left behind textures.", this.activeTextures.Count);
|
||||||
|
|
||||||
foreach (var activeTexture in this.activeTextures)
|
foreach (var activeTexture in this.activeTextures)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
private string commandText = string.Empty;
|
private string commandText = string.Empty;
|
||||||
private string textFilter = string.Empty;
|
private string textFilter = string.Empty;
|
||||||
private string selectedSource = "DalamudInternal";
|
private string selectedSource = "DalamudInternal";
|
||||||
|
private string pluginFilter = string.Empty;
|
||||||
|
|
||||||
private bool filterShowUncaughtExceptions;
|
private bool filterShowUncaughtExceptions;
|
||||||
private bool showFilterToolbar;
|
private bool showFilterToolbar;
|
||||||
|
|
@ -149,7 +150,7 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
const string regexErrorString = "Regex Filter Error";
|
const string regexErrorString = "Regex Filter Error";
|
||||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X / 2.0f - ImGui.CalcTextSize(regexErrorString).X / 2.0f);
|
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X / 2.0f - ImGui.CalcTextSize(regexErrorString).X / 2.0f);
|
||||||
ImGui.TextColored(KnownColor.OrangeRed.Vector(), regexErrorString);
|
ImGui.TextColored(ImGuiColors.DalamudRed, regexErrorString);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55 * ImGuiHelpers.GlobalScale), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55 * ImGuiHelpers.GlobalScale), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
||||||
|
|
@ -475,14 +476,24 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
if (ImGui.BeginCombo("##Sources", this.selectedSource))
|
if (ImGui.BeginCombo("##Sources", this.selectedSource, ImGuiComboFlags.HeightLarge))
|
||||||
{
|
{
|
||||||
var sourceNames = Service<PluginManager>.Get().InstalledPlugins
|
var sourceNames = Service<PluginManager>.Get().InstalledPlugins
|
||||||
.Select(p => p.Manifest.InternalName)
|
.Select(p => p.Manifest.InternalName)
|
||||||
.OrderBy(s => s)
|
.OrderBy(s => s)
|
||||||
.Prepend("DalamudInternal")
|
.Prepend("DalamudInternal")
|
||||||
|
.Where(name => this.pluginFilter is "" || new FuzzyMatcher(this.pluginFilter.ToLowerInvariant(), MatchMode.Fuzzy).Matches(name.ToLowerInvariant()) != 0)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
ImGui.InputTextWithHint("##PluginSearchFilter", "Filter Plugin List", ref this.pluginFilter, 2048);
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (!sourceNames.Any())
|
||||||
|
{
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudRed, "No Results");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var selectable in sourceNames)
|
foreach (var selectable in sourceNames)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(selectable, this.selectedSource == selectable))
|
if (ImGui.Selectable(selectable, this.selectedSource == selectable))
|
||||||
|
|
|
||||||
266
Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs
Normal file
266
Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal.Windows;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A window for displaying IME details.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class DalamudImeWindow : Window
|
||||||
|
{
|
||||||
|
private const int ImePageSize = 9;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudImeWindow"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public DalamudImeWindow()
|
||||||
|
: base(
|
||||||
|
"Dalamud IME",
|
||||||
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoBackground)
|
||||||
|
{
|
||||||
|
this.Size = default(Vector2);
|
||||||
|
|
||||||
|
this.RespectCloseHotkey = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void PostDraw()
|
||||||
|
{
|
||||||
|
if (Service<DalamudIme>.GetNullable() is not { } ime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var viewport = ime.AssociatedViewport;
|
||||||
|
if (viewport.NativePtr is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var drawCand = ime.ImmCand.Count != 0;
|
||||||
|
var drawConv = drawCand || ime.ShowPartialConversion;
|
||||||
|
var drawIme = ime.InputModeIcon != 0;
|
||||||
|
var imeIconFont = InterfaceManager.DefaultFont;
|
||||||
|
|
||||||
|
var pad = ImGui.GetStyle().WindowPadding;
|
||||||
|
var candTextSize = ImGui.CalcTextSize(ime.ImmComp == string.Empty ? " " : ime.ImmComp);
|
||||||
|
|
||||||
|
var native = ime.ImmCandNative;
|
||||||
|
var totalIndex = native.dwSelection + 1;
|
||||||
|
var totalSize = native.dwCount;
|
||||||
|
|
||||||
|
var pageStart = native.dwPageStart;
|
||||||
|
var pageIndex = (pageStart / ImePageSize) + 1;
|
||||||
|
var pageCount = (totalSize / ImePageSize) + 1;
|
||||||
|
var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})";
|
||||||
|
|
||||||
|
// Calc the window size.
|
||||||
|
var maxTextWidth = 0f;
|
||||||
|
for (var i = 0; i < ime.ImmCand.Count; i++)
|
||||||
|
{
|
||||||
|
var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}");
|
||||||
|
maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X;
|
||||||
|
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X
|
||||||
|
? maxTextWidth
|
||||||
|
: ImGui.CalcTextSize(ime.ImmComp).X;
|
||||||
|
|
||||||
|
var numEntries = (drawCand ? ime.ImmCand.Count + 1 : 0) + 1 + (drawIme ? 1 : 0);
|
||||||
|
var spaceY = ImGui.GetStyle().ItemSpacing.Y;
|
||||||
|
var imeWindowHeight = (spaceY * (numEntries - 1)) + (candTextSize.Y * numEntries);
|
||||||
|
var windowSize = new Vector2(maxTextWidth, imeWindowHeight) + (pad * 2);
|
||||||
|
|
||||||
|
// 1. Figure out the expanding direction.
|
||||||
|
var expandUpward = ime.CursorPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y;
|
||||||
|
var windowPos = ime.CursorPos - pad;
|
||||||
|
if (expandUpward)
|
||||||
|
{
|
||||||
|
windowPos.Y -= windowSize.Y - candTextSize.Y - (pad.Y * 2);
|
||||||
|
if (drawIme)
|
||||||
|
windowPos.Y += candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (drawIme)
|
||||||
|
windowPos.Y -= candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Contain within the viewport. Do not use clamp, as the target window might be too small.
|
||||||
|
if (windowPos.X < viewport.WorkPos.X)
|
||||||
|
windowPos.X = viewport.WorkPos.X;
|
||||||
|
else if (windowPos.X + windowSize.X > viewport.WorkPos.X + viewport.WorkSize.X)
|
||||||
|
windowPos.X = (viewport.WorkPos.X + viewport.WorkSize.X) - windowSize.X;
|
||||||
|
if (windowPos.Y < viewport.WorkPos.Y)
|
||||||
|
windowPos.Y = viewport.WorkPos.Y;
|
||||||
|
else if (windowPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y)
|
||||||
|
windowPos.Y = (viewport.WorkPos.Y + viewport.WorkSize.Y) - windowSize.Y;
|
||||||
|
|
||||||
|
var cursor = windowPos + pad;
|
||||||
|
|
||||||
|
// Draw the ime window.
|
||||||
|
var drawList = ImGui.GetForegroundDrawList(viewport);
|
||||||
|
|
||||||
|
// Draw the background rect for candidates.
|
||||||
|
if (drawCand)
|
||||||
|
{
|
||||||
|
Vector2 candRectLt, candRectRb;
|
||||||
|
if (!expandUpward)
|
||||||
|
{
|
||||||
|
candRectLt = windowPos + candTextSize with { X = 0 } + pad with { X = 0 };
|
||||||
|
candRectRb = windowPos + windowSize;
|
||||||
|
if (drawIme)
|
||||||
|
candRectLt.Y += spaceY + candTextSize.Y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
candRectLt = windowPos;
|
||||||
|
candRectRb = windowPos + (windowSize - candTextSize with { X = 0 } - pad with { X = 0 });
|
||||||
|
if (drawIme)
|
||||||
|
candRectRb.Y -= spaceY + candTextSize.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList.AddRectFilled(
|
||||||
|
candRectLt,
|
||||||
|
candRectRb,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.WindowBg),
|
||||||
|
ImGui.GetStyle().WindowRounding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expandUpward && drawIme)
|
||||||
|
{
|
||||||
|
for (var dx = -2; dx <= 2; dx++)
|
||||||
|
{
|
||||||
|
for (var dy = -2; dy <= 2; dy++)
|
||||||
|
{
|
||||||
|
if (dx != 0 || dy != 0)
|
||||||
|
{
|
||||||
|
imeIconFont.RenderChar(
|
||||||
|
drawList,
|
||||||
|
imeIconFont.FontSize,
|
||||||
|
cursor + new Vector2(dx, dy),
|
||||||
|
ImGui.GetColorU32(ImGuiCol.WindowBg),
|
||||||
|
ime.InputModeIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imeIconFont.RenderChar(
|
||||||
|
drawList,
|
||||||
|
imeIconFont.FontSize,
|
||||||
|
cursor,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.Text),
|
||||||
|
ime.InputModeIcon);
|
||||||
|
cursor.Y += candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expandUpward && drawConv)
|
||||||
|
{
|
||||||
|
DrawTextBeingConverted();
|
||||||
|
cursor.Y += candTextSize.Y + spaceY;
|
||||||
|
|
||||||
|
// Add a separator.
|
||||||
|
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawCand)
|
||||||
|
{
|
||||||
|
// Add the candidate words.
|
||||||
|
for (var i = 0; i < ime.ImmCand.Count; i++)
|
||||||
|
{
|
||||||
|
var selected = i == (native.dwSelection % ImePageSize);
|
||||||
|
var color = ImGui.GetColorU32(ImGuiCol.Text);
|
||||||
|
if (selected)
|
||||||
|
color = ImGui.GetColorU32(ImGuiCol.NavHighlight);
|
||||||
|
|
||||||
|
drawList.AddText(cursor, color, $"{i + 1}. {ime.ImmCand[i]}");
|
||||||
|
cursor.Y += candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a separator
|
||||||
|
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
|
||||||
|
|
||||||
|
// Add the pages infomation.
|
||||||
|
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), pageInfo);
|
||||||
|
cursor.Y += candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expandUpward && drawConv)
|
||||||
|
{
|
||||||
|
// Add a separator.
|
||||||
|
drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator));
|
||||||
|
|
||||||
|
DrawTextBeingConverted();
|
||||||
|
cursor.Y += candTextSize.Y + spaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expandUpward && drawIme)
|
||||||
|
{
|
||||||
|
for (var dx = -2; dx <= 2; dx++)
|
||||||
|
{
|
||||||
|
for (var dy = -2; dy <= 2; dy++)
|
||||||
|
{
|
||||||
|
if (dx != 0 || dy != 0)
|
||||||
|
{
|
||||||
|
imeIconFont.RenderChar(
|
||||||
|
drawList,
|
||||||
|
imeIconFont.FontSize,
|
||||||
|
cursor + new Vector2(dx, dy),
|
||||||
|
ImGui.GetColorU32(ImGuiCol.WindowBg),
|
||||||
|
ime.InputModeIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imeIconFont.RenderChar(
|
||||||
|
drawList,
|
||||||
|
imeIconFont.FontSize,
|
||||||
|
cursor,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.Text),
|
||||||
|
ime.InputModeIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
void DrawTextBeingConverted()
|
||||||
|
{
|
||||||
|
// Draw the text background.
|
||||||
|
drawList.AddRectFilled(
|
||||||
|
cursor - (pad / 2),
|
||||||
|
cursor + candTextSize + (pad / 2),
|
||||||
|
ImGui.GetColorU32(ImGuiCol.WindowBg));
|
||||||
|
|
||||||
|
// If only a part of the full text is marked for conversion, then draw background for the part being edited.
|
||||||
|
if (ime.PartialConversionFrom != 0 || ime.PartialConversionTo != ime.ImmComp.Length)
|
||||||
|
{
|
||||||
|
var part1 = ime.ImmComp[..ime.PartialConversionFrom];
|
||||||
|
var part2 = ime.ImmComp[..ime.PartialConversionTo];
|
||||||
|
var size1 = ImGui.CalcTextSize(part1);
|
||||||
|
var size2 = ImGui.CalcTextSize(part2);
|
||||||
|
drawList.AddRectFilled(
|
||||||
|
cursor + size1 with { Y = 0 },
|
||||||
|
cursor + size2,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TextSelectedBg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the text being converted.
|
||||||
|
drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp);
|
||||||
|
|
||||||
|
// Draw the caret inside the composition string.
|
||||||
|
if (DalamudIme.ShowCursorInInputText)
|
||||||
|
{
|
||||||
|
var partBeforeCaret = ime.ImmComp[..ime.CompositionCursorOffset];
|
||||||
|
var sizeBeforeCaret = ImGui.CalcTextSize(partBeforeCaret);
|
||||||
|
drawList.AddLine(
|
||||||
|
cursor + sizeBeforeCaret with { Y = 0 },
|
||||||
|
cursor + sizeBeforeCaret,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.Text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -130,7 +131,7 @@ public class AddonLifecycleWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var color = receiveEventListener.Hook.IsEnabled ? KnownColor.Green.Vector() : KnownColor.OrangeRed.Vector();
|
var color = receiveEventListener.Hook.IsEnabled ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed;
|
||||||
var text = receiveEventListener.Hook.IsEnabled ? "Enabled" : "Disabled";
|
var text = receiveEventListener.Hook.IsEnabled ? "Enabled" : "Disabled";
|
||||||
ImGui.TextColored(color, text);
|
ImGui.TextColored(color, text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -154,7 +155,7 @@ public class IconBrowserWidget : IDataWindowWidget
|
||||||
this.nullValues.Add(iconId);
|
this.nullValues.Add(iconId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.GetWindowDrawList().AddRect(cursor, cursor + this.iconSize, ImGui.GetColorU32(KnownColor.White.Vector()));
|
ImGui.GetWindowDrawList().AddRect(cursor, cursor + this.iconSize, ImGui.GetColorU32(ImGuiColors.DalamudWhite));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Keys;
|
|
||||||
using Dalamud.Game.Gui.Internal;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A window for displaying IME details.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class ImeWindow : Window
|
|
||||||
{
|
|
||||||
private const int ImePageSize = 9;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ImeWindow"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public ImeWindow()
|
|
||||||
: base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground)
|
|
||||||
{
|
|
||||||
this.Size = new Vector2(100, 200);
|
|
||||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
|
||||||
|
|
||||||
this.RespectCloseHotkey = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Draw()
|
|
||||||
{
|
|
||||||
if (this.IsOpen && Service<KeyState>.Get()[VirtualKey.SHIFT]) Service<DalamudInterface>.Get().CloseImeWindow();
|
|
||||||
var ime = Service<DalamudIME>.GetNullable();
|
|
||||||
|
|
||||||
if (ime == null || !ime.IsEnabled)
|
|
||||||
{
|
|
||||||
ImGui.Text("IME is unavailable.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImGui.Text($"{ime.GetCursorPos()}");
|
|
||||||
// ImGui.Text($"{ImGui.GetWindowViewport().WorkSize}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void PostDraw()
|
|
||||||
{
|
|
||||||
if (this.IsOpen && Service<KeyState>.Get()[VirtualKey.SHIFT]) Service<DalamudInterface>.Get().CloseImeWindow();
|
|
||||||
var ime = Service<DalamudIME>.GetNullable();
|
|
||||||
|
|
||||||
if (ime == null || !ime.IsEnabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var maxTextWidth = 0f;
|
|
||||||
var textHeight = ImGui.CalcTextSize(ime.ImmComp).Y;
|
|
||||||
|
|
||||||
var native = ime.ImmCandNative;
|
|
||||||
var totalIndex = native.Selection + 1;
|
|
||||||
var totalSize = native.Count;
|
|
||||||
|
|
||||||
var pageStart = native.PageStart;
|
|
||||||
var pageIndex = (pageStart / ImePageSize) + 1;
|
|
||||||
var pageCount = (totalSize / ImePageSize) + 1;
|
|
||||||
var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})";
|
|
||||||
|
|
||||||
// Calc the window size
|
|
||||||
for (var i = 0; i < ime.ImmCand.Count; i++)
|
|
||||||
{
|
|
||||||
var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}");
|
|
||||||
maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X;
|
|
||||||
}
|
|
||||||
|
|
||||||
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X;
|
|
||||||
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X ? maxTextWidth : ImGui.CalcTextSize(ime.ImmComp).X;
|
|
||||||
|
|
||||||
var imeWindowWidth = maxTextWidth + (2 * ImGui.GetStyle().WindowPadding.X);
|
|
||||||
var imeWindowHeight = (textHeight * (ime.ImmCand.Count + 2)) + (5 * (ime.ImmCand.Count - 1)) + (2 * ImGui.GetStyle().WindowPadding.Y);
|
|
||||||
|
|
||||||
// Calc the window pos
|
|
||||||
var cursorPos = ime.GetCursorPos();
|
|
||||||
var imeWindowMinPos = new Vector2(cursorPos.X, cursorPos.Y);
|
|
||||||
var imeWindowMaxPos = new Vector2(imeWindowMinPos.X + imeWindowWidth, imeWindowMinPos.Y + imeWindowHeight);
|
|
||||||
var gameWindowSize = ImGui.GetWindowViewport().WorkSize;
|
|
||||||
|
|
||||||
var offset = new Vector2(
|
|
||||||
imeWindowMaxPos.X - gameWindowSize.X > 0 ? imeWindowMaxPos.X - gameWindowSize.X : 0,
|
|
||||||
imeWindowMaxPos.Y - gameWindowSize.Y > 0 ? imeWindowMaxPos.Y - gameWindowSize.Y : 0);
|
|
||||||
imeWindowMinPos -= offset;
|
|
||||||
imeWindowMaxPos -= offset;
|
|
||||||
|
|
||||||
var nextDrawPosY = imeWindowMinPos.Y;
|
|
||||||
var drawAreaPosX = imeWindowMinPos.X + ImGui.GetStyle().WindowPadding.X;
|
|
||||||
|
|
||||||
// Draw the ime window
|
|
||||||
var drawList = ImGui.GetForegroundDrawList();
|
|
||||||
// Draw the background rect
|
|
||||||
drawList.AddRectFilled(imeWindowMinPos, imeWindowMaxPos, ImGui.GetColorU32(ImGuiCol.WindowBg), ImGui.GetStyle().WindowRounding);
|
|
||||||
// Add component text
|
|
||||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp);
|
|
||||||
nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y;
|
|
||||||
// Add separator
|
|
||||||
drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator));
|
|
||||||
// Add candidate words
|
|
||||||
for (var i = 0; i < ime.ImmCand.Count; i++)
|
|
||||||
{
|
|
||||||
var selected = i == (native.Selection % ImePageSize);
|
|
||||||
var color = ImGui.GetColorU32(ImGuiCol.Text);
|
|
||||||
if (selected)
|
|
||||||
color = ImGui.GetColorU32(ImGuiCol.NavHighlight);
|
|
||||||
|
|
||||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), color, $"{i + 1}. {ime.ImmCand[i]}");
|
|
||||||
nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add separator
|
|
||||||
drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator));
|
|
||||||
// Add pages infomation
|
|
||||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), pageInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,7 +31,8 @@ public static class ImGuiHelpers
|
||||||
/// This does not necessarily mean you can call drawing functions.
|
/// This does not necessarily mean you can call drawing functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static unsafe bool IsImGuiInitialized =>
|
public static unsafe bool IsImGuiInitialized =>
|
||||||
ImGui.GetCurrentContext() is not 0 && ImGui.GetIO().NativePtr is not null;
|
ImGui.GetCurrentContext() is not (nint)0 // KW: IDEs get mad without the cast, despite being unnecessary
|
||||||
|
&& ImGui.GetIO().NativePtr is not null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the global Dalamud scale; even available before drawing is ready.<br />
|
/// Gets the global Dalamud scale; even available before drawing is ready.<br />
|
||||||
|
|
@ -426,6 +427,26 @@ public static class ImGuiHelpers
|
||||||
/// <param name="ptr">The pointer.</param>
|
/// <param name="ptr">The pointer.</param>
|
||||||
/// <returns>Whether it is empty.</returns>
|
/// <returns>Whether it is empty.</returns>
|
||||||
public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
|
public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the corresponding ImGui viewport ID for the given window handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hwnd">The window handle.</param>
|
||||||
|
/// <returns>The viewport ID, or -1 if not found.</returns>
|
||||||
|
internal static unsafe int FindViewportId(nint hwnd)
|
||||||
|
{
|
||||||
|
if (!IsImGuiInitialized)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
var viewports = new ImVectorWrapper<ImGuiViewportPtr>(&ImGui.GetPlatformIO().NativePtr->Viewports);
|
||||||
|
for (var i = 0; i < viewports.LengthUnsafe; i++)
|
||||||
|
{
|
||||||
|
if (viewports.DataUnsafe[i].PlatformHandle == hwnd)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get data needed for each new frame.
|
/// Get data needed for each new frame.
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,14 @@ namespace Dalamud.Plugin.Internal.Exceptions;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This represents a banned plugin that attempted an operation.
|
/// This represents a banned plugin that attempted an operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class BannedPluginException : PluginException
|
internal class BannedPluginException : PluginPreconditionFailedException
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BannedPluginException"/> class.
|
/// Initializes a new instance of the <see cref="BannedPluginException"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message describing the invalid operation.</param>
|
/// <param name="message">The message describing the invalid operation.</param>
|
||||||
public BannedPluginException(string message)
|
public BannedPluginException(string message)
|
||||||
|
: base(message)
|
||||||
{
|
{
|
||||||
this.Message = message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the message describing the invalid operation.
|
|
||||||
/// </summary>
|
|
||||||
public override string Message { get; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace Dalamud.Plugin.Internal.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An exception to be thrown when policy blocks a plugin from loading.
|
||||||
|
/// </summary>
|
||||||
|
internal class PluginPreconditionFailedException : InvalidPluginOperationException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PluginPreconditionFailedException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to associate with this exception.</param>
|
||||||
|
public PluginPreconditionFailedException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -131,9 +131,16 @@ internal class AssemblyLoadContextBuilder
|
||||||
/// or the default app context.
|
/// or the default app context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyName">The name of the assembly.</param>
|
/// <param name="assemblyName">The name of the assembly.</param>
|
||||||
|
/// <param name="recursive">Pull assmeblies recursively.</param>
|
||||||
/// <returns>The builder.</returns>
|
/// <returns>The builder.</returns>
|
||||||
public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName)
|
public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName, bool recursive)
|
||||||
{
|
{
|
||||||
|
if (!recursive)
|
||||||
|
{
|
||||||
|
this.defaultAssemblies.Add(assemblyName.Name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
var names = new Queue<AssemblyName>(new[] { assemblyName });
|
var names = new Queue<AssemblyName>(new[] { assemblyName });
|
||||||
|
|
||||||
while (names.TryDequeue(out var name))
|
while (names.TryDequeue(out var name))
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ internal class LoaderConfig
|
||||||
/// Gets a list of assemblies which should be unified between the host and the plugin.
|
/// Gets a list of assemblies which should be unified between the host and the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso href="https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md">what-are-shared-types</seealso>
|
/// <seealso href="https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md">what-are-shared-types</seealso>
|
||||||
public ICollection<AssemblyName> SharedAssemblies { get; } = new List<AssemblyName>();
|
public ICollection<(AssemblyName Name, bool Recursive)> SharedAssemblies { get; } = new List<(AssemblyName Name, bool Recursive)>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host.
|
/// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host.
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,18 @@ internal class ManagedLoadContext : AssemblyLoadContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed#algorithm
|
||||||
|
// > These assemblies are loaded (load-by-name) as needed by the runtime.
|
||||||
|
// For load-by-name assembiles, the following will happen in order:
|
||||||
|
// (1) this.Load will be called.
|
||||||
|
// (2) AssemblyLoadContext.Default's cache will be referred for lookup.
|
||||||
|
// (3) Default probing will be done from PLATFORM_RESOURCE_ROOTS and APP_PATHS.
|
||||||
|
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing#managed-assembly-default-probing
|
||||||
|
// > TRUSTED_PLATFORM_ASSEMBLIES: List of platform and application assembly file paths.
|
||||||
|
// > APP_PATHS: is not populated by default and is omitted for most applications.
|
||||||
|
// If we return null here, if the assembly has not been already loaded, the resolution will fail.
|
||||||
|
// Therefore as the final attempt, we try loading from the default load context.
|
||||||
|
return this.defaultLoadContext.LoadFromAssemblyName(assemblyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Nate McMaster, Dalamud team.
|
// Copyright (c) Nate McMaster, Dalamud team.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the Loader root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the Loader root for license information.
|
||||||
|
|
||||||
using System;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
|
|
||||||
|
|
@ -146,11 +146,15 @@ internal class PluginLoader : IDisposable
|
||||||
builder.ShadowCopyNativeLibraries();
|
builder.ShadowCopyNativeLibraries();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var assemblyName in config.SharedAssemblies)
|
foreach (var (assemblyName, recursive) in config.SharedAssemblies)
|
||||||
{
|
{
|
||||||
builder.PreferDefaultLoadContextAssembly(assemblyName);
|
builder.PreferDefaultLoadContextAssembly(assemblyName, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: not adding Dalamud path here as a probing path.
|
||||||
|
// It will be dealt as the last resort from ManagedLoadContext.Load.
|
||||||
|
// See there for more details.
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -953,7 +953,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
autoUpdate ? PluginListInvalidationKind.AutoUpdate : PluginListInvalidationKind.Update,
|
autoUpdate ? PluginListInvalidationKind.AutoUpdate : PluginListInvalidationKind.Update,
|
||||||
updatedList.Select(x => x.InternalName));
|
updatedList.Select(x => x.InternalName));
|
||||||
|
|
||||||
Log.Information("Plugin update OK.");
|
Log.Information("Plugin update OK. {updateCount} plugins updated.", updatedList.Length);
|
||||||
|
|
||||||
return updatedList;
|
return updatedList;
|
||||||
}
|
}
|
||||||
|
|
@ -1578,6 +1578,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
|
|
||||||
private void DetectAvailablePluginUpdates()
|
private void DetectAvailablePluginUpdates()
|
||||||
{
|
{
|
||||||
|
Log.Debug("Starting plugin update check...");
|
||||||
|
|
||||||
lock (this.pluginListLock)
|
lock (this.pluginListLock)
|
||||||
{
|
{
|
||||||
this.updatablePluginsList.Clear();
|
this.updatablePluginsList.Clear();
|
||||||
|
|
@ -1612,10 +1614,12 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Debug("Update check found {updateCount} available updates.", this.updatablePluginsList.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyAvailablePluginsChanged()
|
private void NotifyAvailablePluginsChanged()
|
||||||
{
|
{
|
||||||
this.DetectAvailablePluginUpdates();
|
this.DetectAvailablePluginUpdates();
|
||||||
|
|
||||||
this.OnAvailablePluginsChanged?.InvokeSafely();
|
this.OnAvailablePluginsChanged?.InvokeSafely();
|
||||||
|
|
|
||||||
|
|
@ -315,23 +315,23 @@ internal class LocalPlugin : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pluginManager.IsManifestBanned(this.manifest) && !this.IsDev)
|
if (pluginManager.IsManifestBanned(this.manifest) && !this.IsDev)
|
||||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
throw new BannedPluginException($"Unable to load {this.Name} as it was banned");
|
||||||
|
|
||||||
if (this.manifest.ApplicableVersion < dalamud.StartInfo.GameVersion)
|
if (this.manifest.ApplicableVersion < dalamud.StartInfo.GameVersion)
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
throw new PluginPreconditionFailedException($"Unable to load {this.Name}, game is newer than applicable version {this.manifest.ApplicableVersion}");
|
||||||
|
|
||||||
if (this.manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !pluginManager.LoadAllApiLevels)
|
if (this.manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !pluginManager.LoadAllApiLevels)
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
throw new PluginPreconditionFailedException($"Unable to load {this.Name}, incompatible API level {this.manifest.DalamudApiLevel}");
|
||||||
|
|
||||||
// We might want to throw here?
|
// We might want to throw here?
|
||||||
if (!this.IsWantedByAnyProfile)
|
if (!this.IsWantedByAnyProfile)
|
||||||
Log.Warning("{Name} is loading, but isn't wanted by any profile", this.Name);
|
Log.Warning("{Name} is loading, but isn't wanted by any profile", this.Name);
|
||||||
|
|
||||||
if (this.IsOrphaned)
|
if (this.IsOrphaned)
|
||||||
throw new InvalidPluginOperationException($"Plugin {this.Name} had no associated repo.");
|
throw new PluginPreconditionFailedException($"Plugin {this.Name} had no associated repo");
|
||||||
|
|
||||||
if (!this.CheckPolicy())
|
if (!this.CheckPolicy())
|
||||||
throw new InvalidPluginOperationException("Plugin was not loaded as per policy");
|
throw new PluginPreconditionFailedException($"Unable to load {this.Name} as a load policy forbids it");
|
||||||
|
|
||||||
this.State = PluginState.Loading;
|
this.State = PluginState.Loading;
|
||||||
Log.Information($"Loading {this.DllFile.Name}");
|
Log.Information($"Loading {this.DllFile.Name}");
|
||||||
|
|
@ -440,7 +440,10 @@ internal class LocalPlugin : IDisposable
|
||||||
{
|
{
|
||||||
this.State = PluginState.LoadError;
|
this.State = PluginState.LoadError;
|
||||||
|
|
||||||
if (ex is not BannedPluginException)
|
// If a precondition fails, don't record it as an error, as it isn't really.
|
||||||
|
if (ex is PluginPreconditionFailedException)
|
||||||
|
Log.Warning(ex.Message);
|
||||||
|
else
|
||||||
Log.Error(ex, $"Error while loading {this.Name}");
|
Log.Error(ex, $"Error while loading {this.Name}");
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
|
|
@ -625,8 +628,18 @@ internal class LocalPlugin : IDisposable
|
||||||
config.IsUnloadable = true;
|
config.IsUnloadable = true;
|
||||||
config.LoadInMemory = true;
|
config.LoadInMemory = true;
|
||||||
config.PreferSharedTypes = false;
|
config.PreferSharedTypes = false;
|
||||||
config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName());
|
|
||||||
config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName());
|
// Pin Lumina and its dependencies recursively (compatibility behavior).
|
||||||
|
// It currently only pulls in System.* anyway.
|
||||||
|
// TODO(api10): Remove this. We don't want to pin Lumina anymore, plugins should be able to provide their own.
|
||||||
|
config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true));
|
||||||
|
config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true));
|
||||||
|
|
||||||
|
// Make sure that plugins do not load their own Dalamud assembly.
|
||||||
|
// We do not pin this recursively; if a plugin loads its own assembly of Dalamud, it is always wrong,
|
||||||
|
// but plugins may load other versions of assemblies that Dalamud depends on.
|
||||||
|
config.SharedAssemblies.Add((typeof(EntryPoint).Assembly.GetName(), false));
|
||||||
|
config.SharedAssemblies.Add((typeof(Common.DalamudStartInfo).Assembly.GetName(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureLoader()
|
private void EnsureLoader()
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3364dfea769b79e43aebaa955b6b98ec1d6eb458
|
Subproject commit bbc4b994254d6913f51da3a20fad9bf4b8c986e5
|
||||||
Loading…
Add table
Add a link
Reference in a new issue