diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7ccf0c2d1..d780df2fc 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@v2
with:
repository: goatcorp/dalamud-distrib
- ssh-key: ${{ secrets.DEPLOY_SSH }}
+ token: ${{ secrets.UPDATE_PAT }}
- uses: actions/download-artifact@v2
with:
name: dalamud-artifact
@@ -96,13 +96,8 @@ jobs:
- name: Commit changes
shell: bash
env:
- DEPLOY_SSH: ${{ secrets.DEPLOY_SSH }}
- GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no"
GH_BRANCH: ${{ steps.extract_branch.outputs.branch }}
run: |
- eval "$(ssh-agent -s)"
- ssh-add - <<< "${DEPLOY_SSH}"
-
git config --global user.name "Actions User"
git config --global user.email "actions@github.com"
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj
index 2e0f19aca..d4cb1528f 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -66,6 +66,7 @@
_DEBUG;%(PreprocessorDefinitions)
Use
26812
+ false
false
diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
index aea9fe452..25919af07 100644
--- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
+++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
@@ -28,7 +28,7 @@
-
+
all
diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs
index 5ed6d02ac..d352ad2c8 100644
--- a/Dalamud.CorePlugin/PluginImpl.cs
+++ b/Dalamud.CorePlugin/PluginImpl.cs
@@ -54,7 +54,7 @@ namespace Dalamud.CorePlugin
/// Initializes a new instance of the class.
///
/// Dalamud plugin interface.
- public PluginImpl(DalamudPluginInterface pluginInterface)
+ public PluginImpl(DalamudPluginInterface pluginInterface, PluginLog log)
{
try
{
@@ -68,7 +68,7 @@ namespace Dalamud.CorePlugin
Service.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
- PluginLog.Information("CorePlugin ctor!");
+ log.Information("CorePlugin ctor!");
}
catch (Exception ex)
{
diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs
index c29fada83..fbefbd92a 100644
--- a/Dalamud.Injector/EntryPoint.cs
+++ b/Dalamud.Injector/EntryPoint.cs
@@ -200,15 +200,21 @@ namespace Dalamud.Injector
var logFile = new FileInfo(logPath);
if (!logFile.Exists)
+ {
logFile.Create();
+ }
if (logFile.Length <= cullingFileSize)
+ {
return;
+ }
var amountToCull = logFile.Length - cullingFileSize;
if (amountToCull < bufferSize)
+ {
return;
+ }
using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
@@ -247,7 +253,6 @@ namespace Dalamud.Injector
var workingDirectory = startInfo.WorkingDirectory;
var configurationPath = startInfo.ConfigurationPath;
var pluginDirectory = startInfo.PluginDirectory;
- var defaultPluginDirectory = startInfo.DefaultPluginDirectory;
var assetDirectory = startInfo.AssetDirectory;
var delayInitializeMs = startInfo.DelayInitializeMs;
var logName = startInfo.LogName;
@@ -257,25 +262,41 @@ namespace Dalamud.Injector
for (var i = 2; i < args.Count; i++)
{
if (args[i].StartsWith(key = "--dalamud-working-directory="))
+ {
workingDirectory = args[i][key.Length..];
+ }
else if (args[i].StartsWith(key = "--dalamud-configuration-path="))
+ {
configurationPath = args[i][key.Length..];
+ }
else if (args[i].StartsWith(key = "--dalamud-plugin-directory="))
+ {
pluginDirectory = args[i][key.Length..];
- else if (args[i].StartsWith(key = "--dalamud-dev-plugin-directory="))
- defaultPluginDirectory = args[i][key.Length..];
+ }
else if (args[i].StartsWith(key = "--dalamud-asset-directory="))
+ {
assetDirectory = args[i][key.Length..];
+ }
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
+ {
delayInitializeMs = int.Parse(args[i][key.Length..]);
+ }
else if (args[i].StartsWith(key = "--dalamud-client-language="))
+ {
languageStr = args[i][key.Length..].ToLowerInvariant();
+ }
else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
+ {
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
+ }
else if (args[i].StartsWith(key = "--logname="))
+ {
logName = args[i][key.Length..];
+ }
else
+ {
continue;
+ }
args.RemoveAt(i);
i--;
@@ -287,33 +308,49 @@ namespace Dalamud.Injector
workingDirectory ??= Directory.GetCurrentDirectory();
configurationPath ??= Path.Combine(xivlauncherDir, "dalamudConfig.json");
pluginDirectory ??= Path.Combine(xivlauncherDir, "installedPlugins");
- defaultPluginDirectory ??= Path.Combine(xivlauncherDir, "devPlugins");
assetDirectory ??= Path.Combine(xivlauncherDir, "dalamudAssets", "dev");
ClientLanguage clientLanguage;
if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "english").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.English;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "japanese").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.Japanese;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "日本語").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.Japanese;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "german").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.German;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "deutsch").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.German;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "french").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.French;
+ }
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "français").Length))] == key[0..len])
+ {
clientLanguage = ClientLanguage.French;
+ }
else if (int.TryParse(languageStr, out var languageInt) && Enum.IsDefined((ClientLanguage)languageInt))
+ {
clientLanguage = (ClientLanguage)languageInt;
+ }
else
+ {
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
+ }
startInfo.WorkingDirectory = workingDirectory;
startInfo.ConfigurationPath = configurationPath;
startInfo.PluginDirectory = pluginDirectory;
- startInfo.DefaultPluginDirectory = defaultPluginDirectory;
startInfo.AssetDirectory = assetDirectory;
startInfo.Language = clientLanguage;
startInfo.DelayInitializeMs = delayInitializeMs;
@@ -350,10 +387,14 @@ namespace Dalamud.Injector
exeSpaces += " ";
if (particularCommand is null or "help")
+ {
Console.WriteLine("{0} help [command]", exeName);
+ }
if (particularCommand is null or "inject")
+ {
Console.WriteLine("{0} inject [-h/--help] [-a/--all] [--warn] [--fix-acl] [--se-debug-privilege] [pid1] [pid2] [pid3] ...", exeName);
+ }
if (particularCommand is null or "launch")
{
@@ -367,7 +408,7 @@ namespace Dalamud.Injector
}
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
- Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-dev-plugin-directory=path]");
+ Console.WriteLine(" [--dalamud-plugin-directory=path]");
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
@@ -431,7 +472,7 @@ namespace Dalamud.Injector
}
else
{
- throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
+ Log.Warning($"\"{args[i]}\" is not a valid command line argument, ignoring.");
}
}
@@ -505,29 +546,53 @@ namespace Dalamud.Injector
}
if (args[i] == "-h" || args[i] == "--help")
+ {
showHelp = true;
+ }
else if (args[i] == "-f" || args[i] == "--fake-arguments")
+ {
useFakeArguments = true;
+ }
else if (args[i] == "--without-dalamud")
+ {
withoutDalamud = true;
+ }
else if (args[i] == "--no-wait")
+ {
waitForGameWindow = false;
+ }
else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix")
+ {
noFixAcl = true;
+ }
else if (args[i] == "-g")
+ {
gamePath = args[++i];
+ }
else if (args[i].StartsWith("--game="))
+ {
gamePath = args[i].Split('=', 2)[1];
+ }
else if (args[i] == "-m")
+ {
mode = args[++i];
+ }
else if (args[i].StartsWith("--mode="))
+ {
mode = args[i].Split('=', 2)[1];
+ }
else if (args[i].StartsWith("--handle-owner="))
+ {
handleOwner = IntPtr.Parse(args[i].Split('=', 2)[1]);
+ }
else if (args[i] == "--")
+ {
parsingGameArgument = true;
+ }
else
- throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
+ {
+ Log.Warning($"\"{args[i]}\" is not a valid command line argument, ignoring.");
+ }
}
var checksumTable = "fX1pGtdS5CAP4_VL";
@@ -536,11 +601,15 @@ namespace Dalamud.Injector
gameArguments = gameArguments.SelectMany(x =>
{
if (!x.StartsWith("//**sqex0003") || !x.EndsWith("**//"))
+ {
return new List() { x };
+ }
var checksum = checksumTable.IndexOf(x[x.Length - 5]);
if (checksum == -1)
+ {
return new List() { x };
+ }
var encData = Convert.FromBase64String(x.Substring(12, x.Length - 12 - 5).Replace('-', '+').Replace('_', '/').Replace('*', '='));
var rawData = new byte[encData.Length];
@@ -554,13 +623,25 @@ namespace Dalamud.Injector
encryptArguments = true;
var args = argDelimiterRegex.Split(rawString).Skip(1).Select(y => string.Join('=', kvDelimiterRegex.Split(y, 2)).Replace(" ", " ")).ToList();
if (!args.Any())
+ {
continue;
+ }
+
if (!args.First().StartsWith("T="))
+ {
continue;
+ }
+
if (!uint.TryParse(args.First().Substring(2), out var tickCount))
+ {
continue;
+ }
+
if (tickCount >> 16 != i)
+ {
continue;
+ }
+
return args.Skip(1);
}
@@ -712,7 +793,9 @@ namespace Dalamud.Injector
if (handleOwner != IntPtr.Zero)
{
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, process.Handle, handleOwner, out processHandleForOwner, 0, false, DuplicateOptions.SameAccess))
+ {
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
+ }
}
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {processHandleForOwner}}}");
@@ -755,7 +838,9 @@ namespace Dalamud.Injector
helperProcess.BeginErrorReadLine();
helperProcess.WaitForExit();
if (helperProcess.ExitCode != 0)
+ {
return -1;
+ }
var result = JsonSerializer.CreateDefault().Deserialize>(new JsonTextReader(helperProcess.StandardOutput));
var pid = result["pid"];
@@ -812,7 +897,9 @@ namespace Dalamud.Injector
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
if (startInfoAddress == 0)
+ {
throw new Exception("Unable to allocate start info JSON");
+ }
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
@@ -847,7 +934,10 @@ namespace Dalamud.Injector
///
private static string EncodeParameterArgument(string argument, bool force = false)
{
- if (argument == null) throw new ArgumentNullException(nameof(argument));
+ if (argument == null)
+ {
+ throw new ArgumentNullException(nameof(argument));
+ }
// Unless we're told otherwise, don't quote unless we actually
// need to do so --- hopefully avoid problems if programs won't
diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index c870fa09d..0d29b8cb7 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -153,6 +153,11 @@ internal sealed class DalamudConfiguration : IServiceType
///
public bool ToggleUiHideDuringGpose { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating whether or not a message containing Dalamud's current version and the number of loaded plugins should be sent at login.
+ ///
+ public bool PrintDalamudWelcomeMsg { get; set; } = true;
+
///
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
///
diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj
index 20a5918b7..116ebd008 100644
--- a/Dalamud/Dalamud.csproj
+++ b/Dalamud/Dalamud.csproj
@@ -8,7 +8,7 @@
- 7.5.0.2
+ 7.6.0.0
XIV Launcher addon framework
$(DalamudVersion)
$(DalamudVersion)
@@ -68,7 +68,7 @@
-
+
diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs
index 658934005..4c8e7566d 100644
--- a/Dalamud/DalamudStartInfo.cs
+++ b/Dalamud/DalamudStartInfo.cs
@@ -30,7 +30,6 @@ public record DalamudStartInfo : IServiceType
this.ConfigurationPath = other.ConfigurationPath;
this.LogName = other.LogName;
this.PluginDirectory = other.PluginDirectory;
- this.DefaultPluginDirectory = other.DefaultPluginDirectory;
this.AssetDirectory = other.AssetDirectory;
this.Language = other.Language;
this.GameVersion = other.GameVersion;
@@ -72,11 +71,6 @@ public record DalamudStartInfo : IServiceType
///
public string? PluginDirectory { get; set; }
- ///
- /// Gets or sets the path to the directory for developer plugins.
- ///
- public string? DefaultPluginDirectory { get; set; }
-
///
/// Gets or sets the path to core Dalamud assets.
///
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index d3c28011f..33e09e221 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -97,7 +97,8 @@ public sealed class EntryPoint
var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", $"{logFileName}.log.old");
#endif
Log.CloseAndFlush();
-
+
+#if DEBUG
var oldFileOld = new FileInfo(oldPathOld);
if (oldFileOld.Exists)
{
@@ -109,6 +110,23 @@ public sealed class EntryPoint
}
CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
+#else
+ try
+ {
+ if (File.Exists(logPath))
+ File.Delete(logPath);
+
+ if (File.Exists(oldPath))
+ File.Delete(oldPath);
+
+ if (File.Exists(oldPathOld))
+ File.Delete(oldPathOld);
+ }
+ catch
+ {
+ // ignored
+ }
+#endif
var config = new LoggerConfiguration()
.WriteTo.Sink(SerilogEventSink.Instance)
@@ -156,6 +174,8 @@ public sealed class EntryPoint
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ var unloadFailed = false;
+
try
{
if (info.DelayInitializeMs > 0)
@@ -180,7 +200,15 @@ public sealed class EntryPoint
dalamud.WaitForUnload();
- ServiceManager.UnloadAllServices();
+ try
+ {
+ ServiceManager.UnloadAllServices();
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Could not unload services.");
+ unloadFailed = true;
+ }
}
catch (Exception ex)
{
@@ -195,6 +223,11 @@ public sealed class EntryPoint
Log.CloseAndFlush();
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
}
+
+ // If we didn't unload services correctly, we need to kill the process.
+ // We will never signal to Framework.
+ if (unloadFailed)
+ Environment.Exit(-1);
}
private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
@@ -327,7 +360,7 @@ public sealed class EntryPoint
}
var pluginInfo = string.Empty;
- var supportText = ", please visit us on Discord for more help.";
+ var supportText = ", please visit us on Discord for more help";
try
{
var pm = Service.GetNullable();
diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index 9250ef70c..4827c0d4f 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -110,7 +110,7 @@ public class ChatHandlers : IServiceType
private readonly DalamudConfiguration configuration = Service.Get();
private bool hasSeenLoadingMsg;
- private bool hasAutoUpdatedPlugins;
+ private bool startedAutoUpdatingPlugins;
[ServiceManager.ServiceConstructor]
private ChatHandlers(ChatGui chatGui)
@@ -129,6 +129,11 @@ public class ChatHandlers : IServiceType
///
public string? LastLink { get; private set; }
+ ///
+ /// Gets a value indicating whether or not auto-updates have already completed this session.
+ ///
+ public bool IsAutoUpdateComplete { get; private set; }
+
///
/// Convert a TextPayload to SeString and wrap in italics payloads.
///
@@ -185,7 +190,7 @@ public class ChatHandlers : IServiceType
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
this.PrintWelcomeMessage();
- if (!this.hasAutoUpdatedPlugins)
+ if (!this.startedAutoUpdatingPlugins)
this.AutoUpdatePlugins();
#if !DEBUG && false
@@ -243,8 +248,10 @@ public class ChatHandlers : IServiceType
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
- chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
- + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
+ if (this.configuration.PrintDalamudWelcomeMsg) {
+ chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
+ + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
+ }
if (this.configuration.PrintPluginsWelcomeMsg)
{
@@ -287,13 +294,16 @@ public class ChatHandlers : IServiceType
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
{
// Plugins aren't ready yet.
+ // TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
return;
}
- this.hasAutoUpdatedPlugins = true;
+ this.startedAutoUpdatingPlugins = true;
- Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
+ Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
{
+ this.IsAutoUpdateComplete = true;
+
if (task.IsFaulted)
{
Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates."));
diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
index 46b285c68..9f8a62faf 100644
--- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
+++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs
@@ -1,10 +1,9 @@
-using System;
using System.Collections;
using System.Collections.Generic;
-using System.Runtime.InteropServices;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
+using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Serilog;
namespace Dalamud.Game.ClientState.Aetherytes;
@@ -15,28 +14,23 @@ namespace Dalamud.Game.ClientState.Aetherytes;
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
-public sealed partial class AetheryteList : IServiceType
+public sealed unsafe partial class AetheryteList : IServiceType
{
[ServiceManager.ServiceDependency]
private readonly ClientState clientState = Service.Get();
- private readonly ClientStateAddressResolver address;
- private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
+
+ private readonly Telepo* telepoInstance = Telepo.Instance();
[ServiceManager.ServiceConstructor]
private AetheryteList()
{
- this.address = this.clientState.AddressResolver;
- this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList);
-
- Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
+ Log.Verbose($"Teleport address 0x{((nint)this.telepoInstance).ToInt64():X}");
}
- private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1);
-
///
/// Gets the amount of Aetherytes the local player has unlocked.
///
- public unsafe int Length
+ public int Length
{
get
{
@@ -45,21 +39,19 @@ public sealed partial class AetheryteList : IServiceType
this.Update();
- if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last)
+ if (this.telepoInstance->TeleportList.First == this.telepoInstance->TeleportList.Last)
return 0;
- return (int)TelepoStruct->TeleportList.Size();
+ return (int)this.telepoInstance->TeleportList.Size();
}
}
- private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo;
-
///
/// Gets a Aetheryte Entry at the specified index.
///
/// Index.
/// A at the specified index.
- public unsafe AetheryteEntry? this[int index]
+ public AetheryteEntry? this[int index]
{
get
{
@@ -71,7 +63,7 @@ public sealed partial class AetheryteList : IServiceType
if (this.clientState.LocalPlayer == null)
return null;
- return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
+ return new AetheryteEntry(this.telepoInstance->TeleportList.Get((ulong)index));
}
}
@@ -81,7 +73,7 @@ public sealed partial class AetheryteList : IServiceType
if (this.clientState.LocalPlayer == null)
return;
- this.updateAetheryteListFunc(this.address.Telepo, 0);
+ this.telepoInstance->UpdateAetheryteList();
}
}
diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
index 34ae44e04..369e620be 100644
--- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
+++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
@@ -62,11 +62,6 @@ public sealed class ClientStateAddressResolver : BaseAddressResolver
///
public IntPtr ConditionFlags { get; private set; }
- ///
- /// Gets the address of the Telepo instance.
- ///
- public IntPtr Telepo { get; private set; }
-
// Functions
///
@@ -80,11 +75,6 @@ public sealed class ClientStateAddressResolver : BaseAddressResolver
///
public IntPtr GamepadPoll { get; private set; }
- ///
- /// Gets the address of the method which updates the list of available teleport locations.
- ///
- public IntPtr UpdateAetheryteList { get; private set; }
-
///
/// Scan for and setup any configured address pointers.
///
@@ -115,9 +105,5 @@ public sealed class ClientStateAddressResolver : BaseAddressResolver
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
-
- this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12");
-
- this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50");
}
}
diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs
index 8fcf59b00..f611a01c6 100644
--- a/Dalamud/Game/ClientState/Conditions/Condition.cs
+++ b/Dalamud/Game/ClientState/Conditions/Condition.cs
@@ -17,7 +17,7 @@ public sealed partial class Condition : IServiceType
///
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
///
- public const int MaxConditionEntries = 100;
+ public const int MaxConditionEntries = 104;
private readonly bool[] cache = new bool[MaxConditionEntries];
diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
index 3c68d2e43..120b76df6 100644
--- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
+++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
@@ -464,4 +464,14 @@ public enum ConditionFlag
/// Unable to execute command while recruiting for a non-cross-world party.
///
RecruitingWorldOnly = 98,
+
+ ///
+ /// Command unavailable in this location.
+ ///
+ Unknown99 = 99,
+
+ ///
+ /// Unable to execute command while editing a portrait.
+ ///
+ EditingPortrait = 100,
}
diff --git a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs
index dd6057d36..f9019ed77 100644
--- a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs
+++ b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs
@@ -10,6 +10,12 @@ public enum BattleNpcSubKind : byte
///
None = 0,
+ ///
+ /// Weak Spots / Battle NPC parts
+ /// Eg: Titan's Heart (Naval), Tioman's left and right wing (Sohm Al), Golem Soulstone (The Sunken Temple of Qarn)
+ ///
+ BattleNpcPart = 1,
+
///
/// BattleNpc representing a Pet.
///
diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs
index 291148708..afc94bd0f 100644
--- a/Dalamud/Game/ClientState/Objects/Types/Character.cs
+++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs
@@ -102,7 +102,15 @@ public unsafe class Character : GameObject
///
/// Gets the status flags.
///
- public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
+ public StatusFlags StatusFlags =>
+ (this.Struct->IsHostile ? StatusFlags.Hostile : StatusFlags.None) |
+ (this.Struct->InCombat ? StatusFlags.InCombat : StatusFlags.None) |
+ (this.Struct->IsWeaponDrawn ? StatusFlags.WeaponOut : StatusFlags.None) |
+ (this.Struct->IsOffhandDrawn ? StatusFlags.OffhandOut : StatusFlags.None) |
+ (this.Struct->IsPartyMember ? StatusFlags.PartyMember : StatusFlags.None) |
+ (this.Struct->IsAllianceMember ? StatusFlags.AllianceMember : StatusFlags.None) |
+ (this.Struct->IsFriend ? StatusFlags.Friend : StatusFlags.None) |
+ (this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None);
///
/// Gets the underlying structure.
diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs
index a94dec1a7..4686d5725 100644
--- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs
+++ b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs
@@ -96,7 +96,7 @@ public sealed class ChatGuiAddressResolver : BaseAddressResolver
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
- this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
+ this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
}
diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs
index d3b479642..59c136416 100644
--- a/Dalamud/Game/Gui/GameGui.cs
+++ b/Dalamud/Game/Gui/GameGui.cs
@@ -11,9 +11,14 @@ using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
+using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET;
using Serilog;
+using SharpDX;
+
+using Vector2 = System.Numerics.Vector2;
+using Vector3 = System.Numerics.Vector3;
namespace Dalamud.Game.Gui;
@@ -28,7 +33,6 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
private readonly GameGuiAddressResolver address;
private readonly GetMatrixSingletonDelegate getMatrixSingleton;
- private readonly ScreenToWorldNativeDelegate screenToWorldNative;
private readonly Hook setGlobalBgmHook;
private readonly Hook handleItemHoverHook;
@@ -66,9 +70,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour);
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton);
-
- this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld);
-
+
this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
@@ -79,9 +81,6 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr GetMatrixSingletonDelegate();
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown);
-
// Hooked delegates
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -246,18 +245,15 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
var matrixSingleton = this.getMatrixSingleton();
// Read current ViewProjectionMatrix plus game window size
- var viewProjectionMatrix = default(SharpDX.Matrix);
+ var viewProjectionMatrix = default(Matrix);
float width, height;
- unsafe
- {
- var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
+ var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer();
- for (var i = 0; i < 16; i++, rawMatrix++)
- viewProjectionMatrix[i] = *rawMatrix;
+ for (var i = 0; i < 16; i++, rawMatrix++)
+ viewProjectionMatrix[i] = *rawMatrix;
- width = *rawMatrix;
- height = *(rawMatrix + 1);
- }
+ width = *rawMatrix;
+ height = *(rawMatrix + 1);
viewProjectionMatrix.Invert();
@@ -277,38 +273,19 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
var clipPos = camPosOne - camPos;
clipPos.Normalize();
- bool isSuccess;
- unsafe
+ // This array is larger than necessary because it contains more info than we currently use
+ var worldPosArray = default(RaycastHit);
+
+ // Theory: this is some kind of flag on what type of things the ray collides with
+ var unknown = stackalloc int[3]
{
- var camPosArray = camPos.ToArray();
- var clipPosArray = clipPos.ToArray();
+ 0x4000,
+ 0x4000,
+ 0x0,
+ };
- // This array is larger than necessary because it contains more info than we currently use
- var worldPosArray = stackalloc float[32];
-
- // Theory: this is some kind of flag on what type of things the ray collides with
- var unknown = stackalloc int[3]
- {
- 0x4000,
- 0x4000,
- 0x0,
- };
-
- fixed (float* pCamPos = camPosArray)
- {
- fixed (float* pClipPos = clipPosArray)
- {
- isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown);
- }
- }
-
- worldPos = new Vector3
- {
- X = worldPosArray[0],
- Y = worldPosArray[1],
- Z = worldPosArray[2],
- };
- }
+ var isSuccess = BGCollisionModule.Raycast2(camPos.ToSystem(), clipPos.ToSystem(), rayDistance, &worldPosArray, unknown);
+ worldPos = worldPosArray.Point;
return isSuccess;
}
@@ -317,7 +294,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// Gets a pointer to the game's UI module.
///
/// IntPtr pointing to UI module.
- public unsafe IntPtr GetUIModule()
+ public IntPtr GetUIModule()
{
var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
if (framework == null)
@@ -336,9 +313,9 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// Name of addon to find.
/// Index of addon to find (1-indexed).
/// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon.
- public unsafe IntPtr GetAddonByName(string name, int index = 1)
+ public IntPtr GetAddonByName(string name, int index = 1)
{
- var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton();
+ var atkStage = AtkStage.GetSingleton();
if (atkStage == null)
return IntPtr.Zero;
@@ -360,7 +337,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// A pointer to the agent interface.
public IntPtr FindAgentInterface(string addonName)
{
- var addon = this.GetAddonByName(addonName, 1);
+ var addon = this.GetAddonByName(addonName);
return this.FindAgentInterface(addon);
}
@@ -369,7 +346,7 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
///
/// The addon address.
/// A pointer to the agent interface.
- public unsafe IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon);
+ public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon);
///
/// Find the agent associated with an addon, if possible.
@@ -430,9 +407,9 @@ public sealed unsafe class GameGui : IDisposable, IServiceType
/// A value indicating whether or not the game is on the title screen.
internal bool IsOnTitleScreen()
{
- var charaSelect = this.GetAddonByName("CharaSelect", 1);
- var charaMake = this.GetAddonByName("CharaMake", 1);
- var titleDcWorldMap = this.GetAddonByName("TitleDCWorldMap", 1);
+ var charaSelect = this.GetAddonByName("CharaSelect");
+ var charaMake = this.GetAddonByName("CharaMake");
+ var titleDcWorldMap = this.GetAddonByName("TitleDCWorldMap");
if (charaMake != nint.Zero || charaSelect != nint.Zero || titleDcWorldMap != nint.Zero)
return false;
diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs
index fda4acb99..e45b07487 100644
--- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs
+++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs
@@ -47,11 +47,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
///
public IntPtr GetMatrixSingleton { get; private set; }
- ///
- /// Gets the address of the native ScreenToWorld method.
- ///
- public IntPtr ScreenToWorld { get; private set; }
-
///
/// Gets the address of the native ToggleUiHide method.
///
@@ -66,13 +61,12 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
protected override void Setup64Bit(SigScanner sig)
{
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
- this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
+ this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ?? 48 89 AE ?? ?? ?? ??");
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
- this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
}
diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
index 9ed5d7ab9..60e61b2f7 100644
--- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs
+++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
@@ -56,7 +56,7 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
this.hookUiModuleRequestMainCommand = Hook.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour);
- var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 ");
+ var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2");
this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour);
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
diff --git a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs
index 4189bc280..89b721a87 100644
--- a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs
+++ b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs
@@ -22,7 +22,7 @@ public sealed class LibcFunctionAddressResolver : BaseAddressResolver
///
protected override void Setup64Bit(SigScanner sig)
{
- this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
- this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
+ this.StdStringFromCstring = sig.ScanText("48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 20 48 8D 41 22 66 C7 41 20 01 01 48 89 01 49 8B D8");
+ this.StdStringDeallocate = sig.ScanText("80 79 21 00 75 12 48 8B 51 08 41 B8 33 00 00 00 48 8B 09 E9 ?? ?? ?? 00 C3");
}
}
diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs
index 5be85bd35..c698ee813 100644
--- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs
+++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs
@@ -22,7 +22,7 @@ public sealed class GameNetworkAddressResolver : BaseAddressResolver
{
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
- this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2");
+ this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2");
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
}
}
diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
index b31c4d217..b3175cad3 100644
--- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
+++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
using Dalamud.Game.Network.Structures;
-using Dalamud.Utility;
+using Dalamud.Networking.Http;
using Newtonsoft.Json;
using Serilog;
@@ -22,6 +22,8 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
+ private readonly HttpClient httpClient = Service.Get().SharedHttpClient;
+
///
/// Initializes a new instance of the class.
///
@@ -97,7 +99,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
var uploadPath = "/upload";
var uploadData = JsonConvert.SerializeObject(uploadObject);
Log.Verbose("{ListingPath}: {ListingUpload}", uploadPath, uploadData);
- await Util.HttpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json"));
+ await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json"));
// ====================================================================================
@@ -133,7 +135,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
var taxUpload = JsonConvert.SerializeObject(taxUploadObject);
Log.Verbose("{TaxPath}: {TaxUpload}", taxPath, taxUpload);
- await Util.HttpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json"));
+ await this.httpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json"));
// ====================================================================================
@@ -175,7 +177,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
message.Headers.Add("Authorization", ApiKey);
message.Content = content;
- await Util.HttpClient.SendAsync(message);
+ await this.httpClient.SendAsync(message);
// ====================================================================================
diff --git a/Dalamud/Interface/ImGuiFileDialog/DriveListLoader.cs b/Dalamud/Interface/ImGuiFileDialog/DriveListLoader.cs
new file mode 100644
index 000000000..5898fefdf
--- /dev/null
+++ b/Dalamud/Interface/ImGuiFileDialog/DriveListLoader.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Dalamud.Interface.ImGuiFileDialog;
+
+///
+/// A drive list loader. Thread-safety guaranteed.
+///
+internal class DriveListLoader
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DriveListLoader()
+ {
+ this.Drives = Array.Empty();
+ }
+
+ ///
+ /// Gets the drive list. This may be incomplete if the loader is still loading.
+ ///
+ public IReadOnlyList Drives { get; private set; }
+
+ ///
+ /// Gets a value indicating whether or not the loader is loading.
+ ///
+ public bool Loading { get; private set; }
+
+ ///
+ /// Loads the drive list, asynchronously.
+ ///
+ /// A representing the asynchronous operation.
+ public async Task LoadDrivesAsync()
+ {
+ this.Loading = true;
+ try
+ {
+ await this.InitDrives();
+ }
+ finally
+ {
+ this.Loading = false;
+ }
+ }
+
+ private async Task InitDrives()
+ {
+ // Force async to avoid this being invoked synchronously unless it's awaited.
+ await Task.Yield();
+ this.Drives = DriveInfo.GetDrives();
+ }
+}
diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs
index c34d047d9..ba71c8cfa 100644
--- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs
+++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs
@@ -12,11 +12,13 @@ public partial class FileDialog
{
private readonly object filesLock = new();
+ private readonly DriveListLoader driveListLoader = new();
+
private List files = new();
private List filteredFiles = new();
private SortingField currentSortingField = SortingField.FileName;
- private bool[] sortDescending = new[] { false, false, false, false };
+ private bool[] sortDescending = { false, false, false, false };
private enum FileStructType
{
@@ -296,12 +298,14 @@ public partial class FileDialog
}
}
+ private IEnumerable GetDrives()
+ {
+ return this.driveListLoader.Drives.Select(drive => new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server));
+ }
+
private void SetupSideBar()
{
- foreach (var drive in DriveInfo.GetDrives())
- {
- this.drives.Add(new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server));
- }
+ _ = this.driveListLoader.LoadDrivesAsync();
var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs
index 1373c9189..07e3bc20f 100644
--- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs
+++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs
@@ -31,7 +31,6 @@ public partial class FileDialog
this.Text = text;
this.Location = location;
this.Icon = icon;
- this.Exists = !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location);
}
public string Text { get; init; }
@@ -40,8 +39,6 @@ public partial class FileDialog
public FontAwesomeIcon Icon { get; init; }
- public bool Exists { get; init; }
-
public bool CheckExistence()
=> !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location);
}
diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs
index c55722b4a..d3be8da95 100644
--- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs
+++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Numerics;
+using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.ImGuiFileDialog;
@@ -316,7 +317,7 @@ public partial class FileDialog
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(5));
var idx = 0;
- foreach (var qa in this.drives.Concat(this.quickAccess).Where(qa => qa.Exists))
+ foreach (var qa in this.GetDrives().Concat(this.quickAccess).Where(qa => !qa.Location.IsNullOrEmpty()))
{
ImGui.PushID(idx++);
ImGui.SetCursorPosX(Scaled(25));
diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs
index fe224be76..aec5e9af4 100644
--- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs
+++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs
@@ -52,7 +52,6 @@ public partial class FileDialog
private float footerHeight = 0;
private string selectedSideBar = string.Empty;
- private List drives = new();
private List quickAccess = new();
///
@@ -259,6 +258,7 @@ public partial class FileDialog
private void SetPath(string path)
{
+ this.searchBuffer = string.Empty;
this.selectedSideBar = string.Empty;
this.currentPath = path;
this.files.Clear();
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index 8848cd481..07d557967 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -517,7 +517,7 @@ internal class DalamudInterface : IDisposable, IServiceType
}
catch (Exception ex)
{
- PluginLog.Error(ex, "Error during OnDraw");
+ Log.Error(ex, "Error during OnDraw");
}
}
@@ -877,7 +877,7 @@ internal class DalamudInterface : IDisposable, IServiceType
foreach (var plugin in pluginManager.InstalledPlugins)
{
// TODO: some more here, state maybe?
- PluginLog.Information($"{plugin.Name}");
+ Log.Information($"{plugin.Name}");
}
}
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 9a8da773c..cde3f1c42 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -20,12 +20,12 @@ using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
+using Dalamud.Logging.Internal;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using ImGuiNET;
using ImGuiScene;
using PInvoke;
-using Serilog;
// general dev notes, here because it's easiest
@@ -47,6 +47,8 @@ namespace Dalamud.Interface.Internal;
[ServiceManager.BlockingEarlyLoadedService]
internal class InterfaceManager : IDisposable, IServiceType
{
+ private static ModuleLog Log = new ModuleLog("IM");
+
private const float DefaultFontSizePt = 12.0f;
private const float DefaultFontSizePx = DefaultFontSizePt * 4.0f / 3.0f;
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
@@ -76,16 +78,41 @@ internal class InterfaceManager : IDisposable, IServiceType
private bool isOverrideGameCursor = true;
[ServiceManager.ServiceConstructor]
- private InterfaceManager()
+ private InterfaceManager(SigScanner sigScanner)
{
+ Log.Information("ctor called");
+
this.dispatchMessageWHook = Hook.FromImport(
null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
this.setCursorHook = Hook.FromImport(
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
+ Log.Information("Import hooks applied");
this.fontBuildSignal = new ManualResetEvent(false);
this.address = new SwapChainVtableResolver();
+ this.address.Setup();
+ Log.Information("Resolver setup complete");
+
+ Log.Information("===== S W A P C H A I N =====");
+ Log.Information($"Is ReShade: {this.address.IsReshade}");
+ Log.Information($"Present address 0x{this.address.Present.ToInt64():X}");
+ Log.Information($"ResizeBuffers address 0x{this.address.ResizeBuffers.ToInt64():X}");
+
+ this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour);
+ this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
+ Log.Information("Present and ResizeBuffers hooked");
+
+ var wndProcAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8");
+ Log.Information($"WndProc address 0x{wndProcAddress.ToInt64():X}");
+ this.processMessageHook = Hook.FromAddress(wndProcAddress, this.ProcessMessageDetour);
+
+ this.setCursorHook.Enable();
+ this.presentHook.Enable();
+ this.resizeBuffersHook.Enable();
+ this.dispatchMessageWHook.Enable();
+ this.processMessageHook.Enable();
+ Log.Information("Hooks enabled");
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -982,10 +1009,9 @@ internal class InterfaceManager : IDisposable, IServiceType
}
[ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(SigScanner sigScanner, Framework framework)
+ private void ContinueConstruction()
{
- this.address.Setup(sigScanner);
- framework.RunOnFrameworkThread(() =>
+ this.framework.RunOnFrameworkThread(() =>
{
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
{
@@ -1004,23 +1030,6 @@ internal class InterfaceManager : IDisposable, IServiceType
{
Log.Error(ex, "Could not enable immersive mode");
}
-
- this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour);
- this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
-
- Log.Verbose("===== S W A P C H A I N =====");
- Log.Verbose($"Present address 0x{this.presentHook!.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.FromAddress(wndProcAddress, this.ProcessMessageDetour);
-
- this.setCursorHook.Enable();
- this.presentHook.Enable();
- this.resizeBuffersHook.Enable();
- this.dispatchMessageWHook.Enable();
- this.processMessageHook.Enable();
});
}
diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs
index 75c94a61d..3b3c4e003 100644
--- a/Dalamud/Interface/Internal/UiDebug.cs
+++ b/Dalamud/Interface/Internal/UiDebug.cs
@@ -19,7 +19,6 @@ internal unsafe class UiDebug
{
private const int UnitListCount = 18;
- private readonly GetAtkStageSingleton getAtkStageSingleton;
private readonly bool[] selectedInList = new bool[UnitListCount];
private readonly string[] listNames = new string[UnitListCount]
{
@@ -52,9 +51,6 @@ internal unsafe class UiDebug
///
public UiDebug()
{
- var sigScanner = Service.Get();
- var getSingletonAddr = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
- this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr);
}
private delegate AtkStage* GetAtkStageSingleton();
@@ -445,7 +441,7 @@ internal unsafe class UiDebug
{
var foundSelected = false;
var noResults = true;
- var stage = this.getAtkStageSingleton();
+ var stage = AtkStage.GetSingleton();
var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
diff --git a/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs b/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
index dd2d911ff..b599fb58f 100644
--- a/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
@@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
+using Dalamud.Networking.Http;
using ImGuiNET;
using Newtonsoft.Json;
@@ -40,7 +40,7 @@ public class BranchSwitcherWindow : Window
{
Task.Run(async () =>
{
- using var client = new HttpClient();
+ var client = Service.Get().SharedHttpClient;
this.branches = await client.GetFromJsonAsync>(BranchInfoUrl);
Debug.Assert(this.branches != null, "this.branches != null");
diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
index 4614fbad2..0aeb0722d 100644
--- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
+using Dalamud.Networking.Http;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
@@ -48,6 +49,9 @@ internal class PluginImageCache : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly InterfaceManager.InterfaceManagerWithScene imWithScene = Service.Get();
+ [ServiceManager.ServiceDependency]
+ private readonly HappyHttpClient happyHttpClient = Service.Get();
+
private readonly BlockingCollection>> downloadQueue = new();
private readonly BlockingCollection> loadQueue = new();
private readonly CancellationTokenSource cancelToken = new();
@@ -535,7 +539,7 @@ internal class PluginImageCache : IDisposable, IServiceType
var bytes = await this.RunInDownloadQueue(
async () =>
{
- var data = await Util.HttpClient.GetAsync(url);
+ var data = await this.happyHttpClient.SharedHttpClient.GetAsync(url);
if (data.StatusCode == HttpStatusCode.NotFound)
return null;
@@ -627,7 +631,9 @@ internal class PluginImageCache : IDisposable, IServiceType
var bytes = await this.RunInDownloadQueue(
async () =>
{
- var data = await Util.HttpClient.GetAsync(url);
+ var httpClient = Service.Get().SharedHttpClient;
+
+ var data = await httpClient.GetAsync(url);
if (data.StatusCode == HttpStatusCode.NotFound)
return null;
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs
index 646285561..a3a965e80 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs
@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net.Http;
using System.Net.Http.Json;
-using System.Threading;
using System.Threading.Tasks;
-
+using Dalamud.Networking.Http;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
-using Serilog;
namespace Dalamud.Interface.Internal.Windows.PluginInstaller;
@@ -42,7 +39,7 @@ internal class DalamudChangelogManager
/// A representing the asynchronous operation.
public async Task ReloadChangelogAsync()
{
- using var client = new HttpClient();
+ var client = Service.Get().SharedHttpClient;
this.Changelogs = null;
var dalamudChangelogs = await client.GetFromJsonAsync>(DalamudChangelogUrl);
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
index a50be905f..3b3804baa 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
@@ -2294,7 +2294,8 @@ internal class PluginInstallerWindow : Window, IDisposable
disabled = disabled || (plugin.IsOrphaned && !plugin.IsLoaded);
// Disable everything if the plugin failed to load
- disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
+ // Now handled by the first case below
+ // disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
// Disable everything if we're loading plugins
disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading;
@@ -2360,7 +2361,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.EndPopup();
}
- if (plugin.State == PluginState.UnloadError && !plugin.IsDev)
+ if (plugin.State is PluginState.UnloadError or PluginState.LoadError or PluginState.DependencyResolutionFailed && !plugin.IsDev)
{
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
@@ -3182,7 +3183,7 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version);
- public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again.");
+ public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerLoadUnloadFailedTooltip", "Plugin load/unload failed, please restart your game and try again.");
public static string PluginButtonToolTip_NeedsToBeInDefault => Loc.Localize("InstallerUnloadNeedsToBeInDefault", "This plugin is in one or more collections. If you want to enable or disable it, please do so by enabling or disabling the collections it is in.\nIf you want to manage it manually, remove it from all collections.");
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs
index 53869ca16..ea345e9cf 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs
@@ -50,6 +50,12 @@ public class SettingsTabGeneral : SettingsTab
c => c.DutyFinderChatMessage,
(v, c) => c.DutyFinderChatMessage = v),
+ new SettingsEntry(
+ Loc.Localize("DalamudSettingsPrintDalamudWelcomeMsg", "Display Dalamud's welcome message"),
+ Loc.Localize("DalamudSettingsPrintDalamudWelcomeMsgHint", "Display Dalamud's welcome message in FFXIV chat when logging in with a character."),
+ c => c.PrintDalamudWelcomeMsg,
+ (v, c) => c.PrintDalamudWelcomeMsg = v),
+
new SettingsEntry(
Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"),
Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character."),
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
index c722807dd..13adccffd 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs
@@ -156,6 +156,14 @@ public class SettingsTabLook : SettingsTab
interfaceManager.RebuildFonts();
}
+ ImGui.SameLine();
+ if (ImGui.Button("24pt##DalamudSettingsGlobalUiScaleReset24"))
+ {
+ this.globalUiScale = 24.0f / 12.0f;
+ ImGui.GetIO().FontGlobalScale = this.globalUiScale;
+ interfaceManager.RebuildFonts();
+ }
+
ImGui.SameLine();
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
{
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs
index 4e69dcb1a..3e73454f3 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs
@@ -67,7 +67,7 @@ public class DevPluginsSettingsEntry : SettingsEntry
}
}
- ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path."));
+ ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add dev plugin load locations.\nThese can be either the directory or DLL path."));
ImGuiHelpers.ScaledDummy(5);
diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs
index 041049643..18d294a3e 100644
--- a/Dalamud/IoC/Internal/ServiceContainer.cs
+++ b/Dalamud/IoC/Internal/ServiceContainer.cs
@@ -6,6 +6,7 @@ using System.Runtime.Serialization;
using System.Threading.Tasks;
using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Internal.Types;
namespace Dalamud.IoC.Internal;
@@ -45,9 +46,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType
///
/// The type of object to create.
/// Scoped objects to be included in the constructor.
+ /// The scope to be used to create scoped services.
/// The created object.
- public async Task