Skip to content

Commit

Permalink
Add /xlcopylog command (#1850)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
anna-is-cute authored Jun 21, 2024
1 parent 5d29427 commit 50dc3d4
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 12 deletions.
21 changes: 21 additions & 0 deletions Dalamud/Interface/Internal/DalamudCommands.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using CheapLoc;
Expand Down Expand Up @@ -143,6 +144,13 @@ private DalamudCommands(CommandManager commandManager)
HelpMessage = "ImGui DEBUG",
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)
Expand Down Expand Up @@ -406,4 +414,17 @@ private void OnOpenProfilerCommand(string command, string arguments)
{
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);
}
}
10 changes: 10 additions & 0 deletions Dalamud/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ SetActiveWindow
HWND_TOPMOST
HWND_NOTOPMOST
SET_WINDOW_POS_FLAGS

OpenClipboard
SetClipboardData
CloseClipboard
DROPFILES
CLIPBOARD_FORMAT
GlobalAlloc
GlobalLock
GlobalUnlock
GLOBAL_ALLOC_FLAGS
92 changes: 80 additions & 12 deletions Dalamud/Utility/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
using Serilog;
using TerraFX.Interop.Windows;
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;

Expand Down Expand Up @@ -476,12 +481,12 @@ public static OSPlatform GetHostPlatform()
case "MacOS": return OSPlatform.OSX;
case "Linux": return OSPlatform.Linux;
}

// 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
// (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.

return IsWine() ? OSPlatform.Linux : OSPlatform.Windows;
}

Expand Down Expand Up @@ -544,7 +549,7 @@ bool MoveNext()
}
}
}
}
}
finally
{
foreach (var enumerator in enumerators)
Expand Down Expand Up @@ -585,7 +590,7 @@ public static void WriteAllTextSafe(string path, string text)
{
WriteAllTextSafe(path, text, Encoding.UTF8);
}

/// <summary>
/// Overwrite text in a file by first writing it to a temporary file, and then
/// moving that file to the path specified.
Expand All @@ -597,7 +602,7 @@ public static void WriteAllTextSafe(string path, string text, Encoding encoding)
{
WriteAllBytesSafe(path, encoding.GetBytes(text));
}

/// <summary>
/// Overwrite data in a file by first writing it to a temporary file, and then
/// moving that file to the path specified.
Expand All @@ -607,13 +612,13 @@ public static void WriteAllTextSafe(string path, string text, Encoding encoding)
public static unsafe void WriteAllBytesSafe(string path, byte[] bytes)
{
ArgumentException.ThrowIfNullOrEmpty(path);

// Open the temp file
var tempPath = path + ".tmp";

using var tempFile = Windows.Win32.PInvoke.CreateFile(
tempPath,
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
tempPath,
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
FILE_SHARE_MODE.FILE_SHARE_NONE,
null,
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
Expand All @@ -622,7 +627,7 @@ public static unsafe void WriteAllBytesSafe(string path, byte[] bytes)

if (tempFile.IsInvalid)
throw new Win32Exception();

// Write the data
uint bytesWritten = 0;
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
Expand All @@ -633,7 +638,7 @@ public static unsafe void WriteAllBytesSafe(string path, byte[] bytes)

if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
throw new Win32Exception();

tempFile.Close();

if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
Expand Down Expand Up @@ -736,6 +741,69 @@ internal static void PrintGameObject(GameObject actor, string tag, bool resolveG
}
}

/// <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)
{
var objType = obj.GetType();
Expand All @@ -750,7 +818,7 @@ private static void ShowSpanProperty(ulong addr, IList<string> path, PropertyInf
"-",
MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
null,
null,
new[] { typeof(object), typeof(IList<string>), typeof(ulong) },
obj.GetType(),
true);
Expand Down Expand Up @@ -907,7 +975,7 @@ private static unsafe void ShowValue(ulong addr, IList<string> path, Type type,
}
}
}

/// <summary>
/// Show a structure in an ImGui context.
/// </summary>
Expand Down

0 comments on commit 50dc3d4

Please sign in to comment.