mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add /xlcopylog command (#1850)
* feat: add /xlcopylog command Add a command to copy the dalamud.log file to the user's clipboard, behaving the same as copying the file in an Explorer window. * chore: fix clientstructs submodule * fix: dispose hGlobal if it's unused * refactor: move file copier to util class
This commit is contained in:
parent
5d2942786f
commit
50dc3d44c2
3 changed files with 111 additions and 12 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -143,6 +144,13 @@ internal class DalamudCommands : IServiceType
|
||||||
HelpMessage = "ImGui DEBUG",
|
HelpMessage = "ImGui DEBUG",
|
||||||
ShowInHelp = false,
|
ShowInHelp = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
commandManager.AddHandler("/xlcopylog", new CommandInfo(this.OnCopyLogCommand)
|
||||||
|
{
|
||||||
|
HelpMessage = Loc.Localize(
|
||||||
|
"DalamudCopyLogHelp",
|
||||||
|
"Copy the dalamud.log file to your clipboard."),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnloadCommand(string command, string arguments)
|
private void OnUnloadCommand(string command, string arguments)
|
||||||
|
|
@ -406,4 +414,17 @@ internal class DalamudCommands : IServiceType
|
||||||
{
|
{
|
||||||
Service<DalamudInterface>.Get().ToggleProfilerWindow();
|
Service<DalamudInterface>.Get().ToggleProfilerWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCopyLogCommand(string command, string arguments)
|
||||||
|
{
|
||||||
|
var chatGui = Service<ChatGui>.Get();
|
||||||
|
var logPath = Path.Join(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"XIVLauncher",
|
||||||
|
"dalamud.log");
|
||||||
|
var message = Util.CopyFilesToClipboard([logPath])
|
||||||
|
? Loc.Localize("DalamudLogCopySuccess", "Log file copied to clipboard.")
|
||||||
|
: Loc.Localize("DalamudLogCopyFailure", "Could not copy log file to clipboard.");
|
||||||
|
chatGui.Print(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,13 @@ SetActiveWindow
|
||||||
HWND_TOPMOST
|
HWND_TOPMOST
|
||||||
HWND_NOTOPMOST
|
HWND_NOTOPMOST
|
||||||
SET_WINDOW_POS_FLAGS
|
SET_WINDOW_POS_FLAGS
|
||||||
|
|
||||||
|
OpenClipboard
|
||||||
|
SetClipboardData
|
||||||
|
CloseClipboard
|
||||||
|
DROPFILES
|
||||||
|
CLIPBOARD_FORMAT
|
||||||
|
GlobalAlloc
|
||||||
|
GlobalLock
|
||||||
|
GlobalUnlock
|
||||||
|
GLOBAL_ALLOC_FLAGS
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ using Lumina.Excel.GeneratedSheets;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
using Windows.Win32.Storage.FileSystem;
|
using Windows.Win32.Storage.FileSystem;
|
||||||
|
using Windows.Win32.System.Memory;
|
||||||
|
using Windows.Win32.System.Ole;
|
||||||
|
|
||||||
|
using HWND = Windows.Win32.Foundation.HWND;
|
||||||
|
using Win32_PInvoke = Windows.Win32.PInvoke;
|
||||||
|
|
||||||
namespace Dalamud.Utility;
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
|
@ -476,12 +481,12 @@ public static class Util
|
||||||
case "MacOS": return OSPlatform.OSX;
|
case "MacOS": return OSPlatform.OSX;
|
||||||
case "Linux": return OSPlatform.Linux;
|
case "Linux": return OSPlatform.Linux;
|
||||||
}
|
}
|
||||||
|
|
||||||
// n.b. we had some fancy code here to check if the Wine host version returned "Darwin" but apparently
|
// n.b. we had some fancy code here to check if the Wine host version returned "Darwin" but apparently
|
||||||
// *all* our Wines report Darwin if exports aren't hidden. As such, it is effectively impossible (without some
|
// *all* our Wines report Darwin if exports aren't hidden. As such, it is effectively impossible (without some
|
||||||
// (very cursed and inaccurate heuristics) to determine if we're on macOS or Linux unless we're explicitly told
|
// (very cursed and inaccurate heuristics) to determine if we're on macOS or Linux unless we're explicitly told
|
||||||
// by our launcher. See commit a7aacb15e4603a367e2f980578271a9a639d8852 for the old check.
|
// by our launcher. See commit a7aacb15e4603a367e2f980578271a9a639d8852 for the old check.
|
||||||
|
|
||||||
return IsWine() ? OSPlatform.Linux : OSPlatform.Windows;
|
return IsWine() ? OSPlatform.Linux : OSPlatform.Windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,7 +549,7 @@ public static class Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
foreach (var enumerator in enumerators)
|
foreach (var enumerator in enumerators)
|
||||||
|
|
@ -585,7 +590,7 @@ public static class Util
|
||||||
{
|
{
|
||||||
WriteAllTextSafe(path, text, Encoding.UTF8);
|
WriteAllTextSafe(path, text, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overwrite text in a file by first writing it to a temporary file, and then
|
/// Overwrite text in a file by first writing it to a temporary file, and then
|
||||||
/// moving that file to the path specified.
|
/// moving that file to the path specified.
|
||||||
|
|
@ -597,7 +602,7 @@ public static class Util
|
||||||
{
|
{
|
||||||
WriteAllBytesSafe(path, encoding.GetBytes(text));
|
WriteAllBytesSafe(path, encoding.GetBytes(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overwrite data in a file by first writing it to a temporary file, and then
|
/// Overwrite data in a file by first writing it to a temporary file, and then
|
||||||
/// moving that file to the path specified.
|
/// moving that file to the path specified.
|
||||||
|
|
@ -607,13 +612,13 @@ public static class Util
|
||||||
public static unsafe void WriteAllBytesSafe(string path, byte[] bytes)
|
public static unsafe void WriteAllBytesSafe(string path, byte[] bytes)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||||
|
|
||||||
// Open the temp file
|
// Open the temp file
|
||||||
var tempPath = path + ".tmp";
|
var tempPath = path + ".tmp";
|
||||||
|
|
||||||
using var tempFile = Windows.Win32.PInvoke.CreateFile(
|
using var tempFile = Windows.Win32.PInvoke.CreateFile(
|
||||||
tempPath,
|
tempPath,
|
||||||
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
|
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
|
||||||
FILE_SHARE_MODE.FILE_SHARE_NONE,
|
FILE_SHARE_MODE.FILE_SHARE_NONE,
|
||||||
null,
|
null,
|
||||||
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
|
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
|
||||||
|
|
@ -622,7 +627,7 @@ public static class Util
|
||||||
|
|
||||||
if (tempFile.IsInvalid)
|
if (tempFile.IsInvalid)
|
||||||
throw new Win32Exception();
|
throw new Win32Exception();
|
||||||
|
|
||||||
// Write the data
|
// Write the data
|
||||||
uint bytesWritten = 0;
|
uint bytesWritten = 0;
|
||||||
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
|
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
|
||||||
|
|
@ -633,7 +638,7 @@ public static class Util
|
||||||
|
|
||||||
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
|
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
|
||||||
throw new Win32Exception();
|
throw new Win32Exception();
|
||||||
|
|
||||||
tempFile.Close();
|
tempFile.Close();
|
||||||
|
|
||||||
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
||||||
|
|
@ -736,6 +741,69 @@ public static class Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy files to the clipboard as if they were copied in Explorer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paths">Full paths to files to be copied.</param>
|
||||||
|
/// <returns>Returns true on success.</returns>
|
||||||
|
internal static unsafe bool CopyFilesToClipboard(IEnumerable<string> paths)
|
||||||
|
{
|
||||||
|
var pathBytes = paths
|
||||||
|
.Select(Encoding.Unicode.GetBytes)
|
||||||
|
.ToArray();
|
||||||
|
var pathBytesSize = pathBytes
|
||||||
|
.Select(bytes => bytes.Length)
|
||||||
|
.Sum();
|
||||||
|
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
|
||||||
|
|
||||||
|
var dropFilesSize = sizeof(DROPFILES);
|
||||||
|
var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle(
|
||||||
|
GLOBAL_ALLOC_FLAGS.GHND,
|
||||||
|
// struct size + size of encoded strings + null terminator for each
|
||||||
|
// string + two null terminators for end of list
|
||||||
|
(uint)(dropFilesSize + sizeWithTerminators + 4));
|
||||||
|
var dropFiles = (DROPFILES*)Win32_PInvoke.GlobalLock(hGlobal);
|
||||||
|
|
||||||
|
*dropFiles = default;
|
||||||
|
dropFiles->fWide = true;
|
||||||
|
dropFiles->pFiles = (uint)dropFilesSize;
|
||||||
|
|
||||||
|
var pathLoc = (byte*)((nint)dropFiles + dropFilesSize);
|
||||||
|
foreach (var bytes in pathBytes)
|
||||||
|
{
|
||||||
|
// copy the encoded strings
|
||||||
|
for (var i = 0; i < bytes.Length; i++)
|
||||||
|
{
|
||||||
|
pathLoc![i] = bytes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// null terminate
|
||||||
|
pathLoc![bytes.Length] = 0;
|
||||||
|
pathLoc[bytes.Length + 1] = 0;
|
||||||
|
pathLoc += bytes.Length + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// double null terminator for end of list
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
pathLoc![i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32_PInvoke.GlobalUnlock(hGlobal);
|
||||||
|
|
||||||
|
if (Win32_PInvoke.OpenClipboard(HWND.Null))
|
||||||
|
{
|
||||||
|
Win32_PInvoke.SetClipboardData(
|
||||||
|
(uint)CLIPBOARD_FORMAT.CF_HDROP,
|
||||||
|
hGlobal);
|
||||||
|
Win32_PInvoke.CloseClipboard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hGlobal.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ShowSpanProperty(ulong addr, IList<string> path, PropertyInfo p, object obj)
|
private static void ShowSpanProperty(ulong addr, IList<string> path, PropertyInfo p, object obj)
|
||||||
{
|
{
|
||||||
var objType = obj.GetType();
|
var objType = obj.GetType();
|
||||||
|
|
@ -750,7 +818,7 @@ public static class Util
|
||||||
"-",
|
"-",
|
||||||
MethodAttributes.Public | MethodAttributes.Static,
|
MethodAttributes.Public | MethodAttributes.Static,
|
||||||
CallingConventions.Standard,
|
CallingConventions.Standard,
|
||||||
null,
|
null,
|
||||||
new[] { typeof(object), typeof(IList<string>), typeof(ulong) },
|
new[] { typeof(object), typeof(IList<string>), typeof(ulong) },
|
||||||
obj.GetType(),
|
obj.GetType(),
|
||||||
true);
|
true);
|
||||||
|
|
@ -907,7 +975,7 @@ public static class Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a structure in an ImGui context.
|
/// Show a structure in an ImGui context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue