diff --git a/Dalamud.Bootstrap/GameProcess.Arguments.cs b/Dalamud.Bootstrap/GameProcess.Arguments.cs index 7ed040889..c026bab94 100644 --- a/Dalamud.Bootstrap/GameProcess.Arguments.cs +++ b/Dalamud.Bootstrap/GameProcess.Arguments.cs @@ -1,4 +1,4 @@ -using Dalamud.Bootstrap.SqexArg; +using Dalamud.Bootstrap.SqexArg; using System; namespace Dalamud.Bootstrap diff --git a/Dalamud.Bootstrap/GameProcess.Win32.Memory.cs b/Dalamud.Bootstrap/GameProcess.Win32.Memory.cs deleted file mode 100644 index c69ec036a..000000000 --- a/Dalamud.Bootstrap/GameProcess.Win32.Memory.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Dalamud.Bootstrap.OS.Windows.Raw; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Dalamud.Bootstrap -{ - public sealed partial class GameProcess : IDisposable - { - /// - /// Reads the process memory. - /// - /// - /// The number of bytes that is actually read. - /// - private int ReadMemory(IntPtr address, Span destination) - { - unsafe - { - fixed (byte* pDest = destination) - { - if (!Kernel32.ReadProcessMemory(m_handle, address, pDest, (IntPtr)destination.Length, out var bytesRead)) - { - ProcessException.ThrowLastOsError(); - } - - // This is okay because destination will never be longer than int.Max - return bytesRead.ToInt32(); - } - } - } - - public void ReadMemoryExact(IntPtr address, Span destination) - { - var totalBytesRead = 0; - - while (totalBytesRead < destination.Length) - { - var bytesRead = ReadMemory(address + totalBytesRead, destination[totalBytesRead..]); - - if (bytesRead == 0) - { - // prolly page fault; there's not much we can do here - var readBeginAddr = address.ToInt64() + totalBytesRead; - var readEndAddr = address.ToInt64() + destination.Length; - - ProcessException.ThrowLastOsError(); - } - - totalBytesRead += bytesRead; - } - } - - private byte[] ReadMemoryExact(IntPtr address, int length) - { - var buffer = new byte[length]; - ReadMemoryExact(address, buffer); - - return buffer; - } - - private void ReadMemoryExact(IntPtr address, ref T value) where T : unmanaged - { - var span = MemoryMarshal.CreateSpan(ref value, 1); // span should never leave this function since it has unbounded lifetime. - var buffer = MemoryMarshal.AsBytes(span); - - ReadMemoryExact(address, buffer); - } - - private IntPtr GetPebAddress() - { - unsafe - { - PROCESS_BASIC_INFORMATION info = default; - - var status = Ntdll.NtQueryInformationProcess(m_handle, PROCESSINFOCLASS.ProcessBasicInformation, &info, sizeof(PROCESS_BASIC_INFORMATION), (IntPtr*)IntPtr.Zero); - - if (!status.Success) - { - throw new ProcessException($"Could not query information on process. (Status: {status})"); - } - - return info.PebBaseAddress; - } - } - } -} diff --git a/Dalamud.Bootstrap/GameProcess.Win32.Process.cs b/Dalamud.Bootstrap/GameProcess.Win32.Process.cs deleted file mode 100644 index dd7ae3f7f..000000000 --- a/Dalamud.Bootstrap/GameProcess.Win32.Process.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Dalamud.Bootstrap.OS.Windows.Raw; -using System; -using System.Text; - -namespace Dalamud.Bootstrap -{ - public sealed partial class GameProcess : IDisposable - { - /// - /// Reads command-line arguments from the process. - /// - public string[] GetProcessArguments() - { - PEB peb = default; - RTL_USER_PROCESS_PARAMETERS procParam = default; - - // Find where the command line is allocated - var pebAddr = GetPebAddress(); - ReadMemoryExact(pebAddr, ref peb); - ReadMemoryExact(peb.ProcessParameters, ref procParam); - - // Read the command line (which is utf16-like string) - var commandLine = ReadMemoryExact(procParam.CommandLine.Buffer, procParam.CommandLine.Length); - - return ParseCommandLineToArguments(commandLine); - } - - /// - /// Returns a time when the process was started. - /// - private DateTime GetCreationTime() - { - if (!Kernel32.GetProcessTimes(m_handle, out var creationTime, out var _, out var _, out var _)) - { - ProcessException.ThrowLastOsError(); - } - - return creationTime.ToDateTime(); - } - - private string[] ParseCommandLineToArguments(ReadOnlySpan commandLine) - { - unsafe - { - char** argv; - int argc; - - fixed (byte* pCommandLine = commandLine) - { - argv = Shell32.CommandLineToArgvW(pCommandLine, out argc); - } - - if (argv == null) - { - ProcessException.ThrowLastOsError(); - } - - // NOTE: argv must be deallocated via LocalFree when we're done - try - { - var arguments = new string[argc]; - - for (var i = 0; i < argc; i++) - { - arguments[i] = new string(argv[i]); - } - - return arguments; - } - finally - { - Kernel32.LocalFree(argv); - } - } - } - - public string GetImageFilePath() - { - var buffer = new StringBuilder(300); - - // From docs: - // On input, specifies the size of the lpExeName buffer, in characters. - // On success, receives the number of characters written to the buffer, not including the null-terminating character. - var size = buffer.Capacity; - - if (!Kernel32.QueryFullProcessImageNameW(m_handle, 0, buffer, ref size)) - { - ProcessException.ThrowLastOsError(); - } - - return buffer.ToString(); - } - } -} diff --git a/Dalamud.Bootstrap/GameProcess.cs b/Dalamud.Bootstrap/GameProcess.cs index 19fcf4885..3ff3c0855 100644 --- a/Dalamud.Bootstrap/GameProcess.cs +++ b/Dalamud.Bootstrap/GameProcess.cs @@ -1,5 +1,6 @@ using Dalamud.Bootstrap.OS; -using Dalamud.Bootstrap.OS.Windows.Raw; +using Dalamud.Bootstrap.OS.Windows; +using Dalamud.Bootstrap.OS.Windows.Raw; using Microsoft.Win32.SafeHandles; using System; @@ -7,42 +8,33 @@ namespace Dalamud.Bootstrap { public sealed partial class GameProcess : IDisposable { - private SafeProcessHandle m_handle; + private Process m_process; - private GameProcess(SafeProcessHandle handle) + // maybe saved acl shit + + private GameProcess(Process process) { - m_handle = handle; - } - - public static GameProcess Create(GameProcessCreationOptions options) - { - - } - - public static GameProcess Open(uint pid) - { - - var handle = OpenHandle(pid, TODO); - - return new GameProcess(handle); - } - - private static SafeProcessHandle OpenHandle(uint pid, PROCESS_ACCESS_RIGHTS access) - { - var handle = Kernel32.OpenProcess((uint)access, false, pid); - - if (handle.IsInvalid) - { - ProcessException.ThrowLastOsError(); - } - - return handle; + m_process = process; } public void Dispose() { - m_handle?.Dispose(); - m_handle = null!; + m_process?.Dispose(); + m_process = null!; } + + // /// + // /// + // /// + // /// A process handle. + // private static void AllowHandleAccess(Process handle) + // { + + // } + + // private static void DenyHandleAccess(SafeProcessHandle handle) + // { + + // } } } diff --git a/Dalamud.Bootstrap/ProcessException.cs b/Dalamud.Bootstrap/OS/ProcessException.cs similarity index 100% rename from Dalamud.Bootstrap/ProcessException.cs rename to Dalamud.Bootstrap/OS/ProcessException.cs diff --git a/Dalamud.Bootstrap/OS/Windows/Process.cs b/Dalamud.Bootstrap/OS/Windows/Process.cs new file mode 100644 index 000000000..082cade9c --- /dev/null +++ b/Dalamud.Bootstrap/OS/Windows/Process.cs @@ -0,0 +1,215 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using Dalamud.Bootstrap.OS.Windows.Raw; +using Microsoft.Win32.SafeHandles; + +namespace Dalamud.Bootstrap.OS.Windows +{ + /// + /// Provides a thin wrapper around process API. + /// + internal sealed class Process : IDisposable + { + /// + /// A process handle that can be passed to PInvoke. Note that it should never outlive a process object. + /// This handle will be disposed when the process object is disposed. + /// + public IntPtr Handle { get; private set; } + + public Process(IntPtr handle) + { + Handle = handle; + } + + public void Dispose() + { + if (Handle != IntPtr.Zero) + { + Kernel32.CloseHandle(Handle); + } + + Handle = IntPtr.Zero; + } + + /// + /// Reads process memory. + /// + /// + /// A number of bytes that is actually read. + /// + /// + /// Thrown when failed to read memory. + /// + public int ReadMemory(IntPtr address, Span destination) + { + unsafe + { + fixed (byte* pDest = destination) + { + if (!Kernel32.ReadProcessMemory(Handle, address, pDest, (IntPtr)destination.Length, out var bytesRead)) + { + ProcessException.ThrowLastOsError(); + } + + // This is okay because destination will never be longer than int.Max + return bytesRead.ToInt32(); + } + } + } + + /// + /// Reads the exact number of bytes required to fill the buffer. + /// + /// + /// Thrown when failed to read memory. + /// + public void ReadMemoryExact(IntPtr address, Span destination) + { + var totalBytesRead = 0; + + while (totalBytesRead < destination.Length) + { + var bytesRead = ReadMemory(address + totalBytesRead, destination[totalBytesRead..]); + + if (bytesRead == 0) + { + // prolly page fault; there's not much we can do here + var readBeginAddr = address.ToInt64() + totalBytesRead; + var readEndAddr = address.ToInt64() + destination.Length; + + ProcessException.ThrowLastOsError(); + } + + totalBytesRead += bytesRead; + } + } + + + /// + /// Thrown when failed to read memory. + /// + public byte[] ReadMemoryExact(IntPtr address, int length) + { + var buffer = new byte[length]; + ReadMemoryExact(address, buffer); + + return buffer; + } + + + /// + /// Thrown when failed to read memory. + /// + public void ReadMemoryExact(IntPtr address, ref T value) where T : unmanaged + { + var span = MemoryMarshal.CreateSpan(ref value, 1); // span should never leave this function since it has unbounded lifetime. + var buffer = MemoryMarshal.AsBytes(span); + + ReadMemoryExact(address, buffer); + } + + + private IntPtr GetPebAddress() + { + unsafe + { + PROCESS_BASIC_INFORMATION info = default; + + var status = Ntdll.NtQueryInformationProcess(Handle, PROCESSINFOCLASS.ProcessBasicInformation, &info, sizeof(PROCESS_BASIC_INFORMATION), (IntPtr*)IntPtr.Zero); + + if (!status.Success) + { + throw new ProcessException($"Could not query information on process. (Status: {status})"); + } + + return info.PebBaseAddress; + } + } + + /// + /// Reads command-line arguments from the process. + /// + public string[] GetProcessArguments() + { + PEB peb = default; + RTL_USER_PROCESS_PARAMETERS procParam = default; + + // Find where the command line is allocated + var pebAddr = GetPebAddress(); + ReadMemoryExact(pebAddr, ref peb); + ReadMemoryExact(peb.ProcessParameters, ref procParam); + + // Read the command line (which is utf16-like string) + var commandLine = ReadMemoryExact(procParam.CommandLine.Buffer, procParam.CommandLine.Length); + + return ParseCommandLineToArguments(commandLine); + } + + /// + /// Returns a time when the process was started. + /// + public DateTime GetCreationTime() + { + if (!Kernel32.GetProcessTimes(Handle, out var creationTime, out var _, out var _, out var _)) + { + ProcessException.ThrowLastOsError(); + } + + return creationTime.ToDateTime(); + } + + private string[] ParseCommandLineToArguments(ReadOnlySpan commandLine) + { + unsafe + { + char** argv; + int argc; + + fixed (byte* pCommandLine = commandLine) + { + argv = Shell32.CommandLineToArgvW(pCommandLine, out argc); + } + + if (argv == null) + { + ProcessException.ThrowLastOsError(); + } + + // NOTE: argv must be deallocated via LocalFree when we're done + try + { + var arguments = new string[argc]; + + for (var i = 0; i < argc; i++) + { + arguments[i] = new string(argv[i]); + } + + return arguments; + } + finally + { + Kernel32.LocalFree(argv); + } + } + } + + public string GetImageFilePath() + { + var buffer = new StringBuilder(300); + + // From docs: + // On input, specifies the size of the lpExeName buffer, in characters. + // On success, receives the number of characters written to the buffer, not including the null-terminating character. + var size = buffer.Capacity; + + if (!Kernel32.QueryFullProcessImageNameW(Handle, 0, buffer, ref size)) + { + ProcessException.ThrowLastOsError(); + } + + return buffer.ToString(); + } + } +} diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs index dad373722..f95a3dbf4 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Advapi32.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; namespace Dalamud.Bootstrap.OS.Windows.Raw @@ -12,5 +13,11 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw [DllImport(Name, CallingConvention = CallingConvention.Winapi)] public static extern uint SetEntriesInAclA(ulong cCountOfExplicitEntries, ref ACL oldAcl, out ACL* NewAcl); + + [DllImport(Name, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, CharSet = CharSet.Unicode)] + public static extern void BuildExplicitAccessWithNameW(out EXPLICIT_ACCESS_W pExplicitAccess, string pTrusteeName, uint AccessPermissions, ACCESS_MODE AccessMode, uint Inheritance); + + [DllImport(Name, CallingConvention = CallingConvention.Winapi)] + public static extern uint GetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo); } } diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs index 2b05fbc65..d2f831795 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Constants.cs @@ -66,4 +66,62 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw SE_SACL_PROTECTED = 0x2000, SE_SELF_RELATIVE = 0x8000, } + + internal enum ACCESS_MODE : uint + { + NOT_USED_ACCESS, + GRANT_ACCESS, + SET_ACCESS, + DENY_ACCESS, + REVOKE_ACCESS, + SET_AUDIT_SUCCESS, + SET_AUDIT_FAILURE, + } + + internal enum MULTIPLE_TRUSTEE_OPERATION : uint + { + NO_MULTIPLE_TRUSTEE, + TRUSTEE_IS_IMPERSONATE, + } + + internal enum TRUSTEE_FORM : uint + { + TRUSTEE_IS_SID, + TRUSTEE_IS_NAME, + TRUSTEE_BAD_FORM, + TRUSTEE_IS_OBJECTS_AND_SID, + TRUSTEE_IS_OBJECTS_AND_NAME, + } + + internal enum TRUSTEE_TYPE : uint + { + TRUSTEE_IS_UNKNOWN, + TRUSTEE_IS_USER, + TRUSTEE_IS_GROUP, + TRUSTEE_IS_DOMAIN, + TRUSTEE_IS_ALIAS, + TRUSTEE_IS_WELL_KNOWN_GROUP, + TRUSTEE_IS_DELETED, + TRUSTEE_IS_INVALID, + TRUSTEE_IS_COMPUTER, + } + + internal enum SE_OBJECT_TYPE : uint + { + + SE_UNKNOWN_OBJECT_TYPE, + SE_FILE_OBJECT, + SE_SERVICE, + SE_PRINTER, + SE_REGISTRY_KEY, + SE_LMSHARE, + SE_KERNEL_OBJECT, + SE_WINDOW_OBJECT, + SE_DS_OBJECT, + SE_DS_OBJECT_ALL, + SE_PROVIDER_DEFINED_OBJECT, + SE_WMIGUID_OBJECT, + SE_REGISTRY_WOW64_32KEY, + SE_REGISTRY_WOW64_64KEY + } } diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Kernel32.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Kernel32.cs index ca346c995..49c78d872 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Kernel32.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Kernel32.cs @@ -1,4 +1,3 @@ -using Microsoft.Win32.SafeHandles; using System; using System.Runtime.InteropServices; using System.Text; @@ -10,33 +9,33 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw private const string Name = "kernel32"; [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] - public static extern SafeProcessHandle OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId); + public static extern IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool TerminateProcess(SafeProcessHandle hProcess, int uExitCode); + public static extern bool TerminateProcess(IntPtr hProcess, int uExitCode); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool ReadProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, void* lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead); + public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool WriteProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, void* lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesWritten); + public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesWritten); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern void* LocalFree(void* hMem); [DllImport(Name, CallingConvention = CallingConvention.Winapi)] - public static extern uint GetProcessId(SafeProcessHandle hProcess); + public static extern uint GetProcessId(IntPtr hProcess); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetProcessTimes(SafeProcessHandle hProcess, out FILETIME lpCreationTime, out FILETIME lpExitTime, out FILETIME lpKernelTime, out FILETIME lpUserTime); + public static extern bool GetProcessTimes(IntPtr hProcess, out FILETIME lpCreationTime, out FILETIME lpExitTime, out FILETIME lpKernelTime, out FILETIME lpUserTime); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool QueryFullProcessImageNameW(SafeProcessHandle hProcess, uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpExeName, ref int lpdwSize); + public static extern bool QueryFullProcessImageNameW(IntPtr hProcess, uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpExeName, ref int lpdwSize); [DllImport(Name, CallingConvention = CallingConvention.Winapi, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs b/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs index 08bdd73fa..6492bf4d7 100644 --- a/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs +++ b/Dalamud.Bootstrap/OS/Windows/Raw/Structures.cs @@ -131,14 +131,23 @@ namespace Dalamud.Bootstrap.OS.Windows.Raw public IntPtr Dacl; } - [StructLayout(LayoutKind.Sequential)] - internal struct EXPLICIT_ACCESS_A + [StructLayout(LayoutKind.Explicit)] + internal unsafe struct TRUSTEE_W + { + public TRUSTEE_W* pMultipleTrustee; + public MULTIPLE_TRUSTEE_OPERATION MULTIPLE_TRUSTEE_OPERATION; + public TRUSTEE_FORM TrusteeForm; + public TRUSTEE_TYPE TrusteeType; + public void* ptstrName; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EXPLICIT_ACCESS_W { - // TODO uint grfAccessPermissions; ACCESS_MODE grfAccessMode; uint grfInheritance; - TRUSTEE_A Trustee; + TRUSTEE_W Trustee; } [StructLayout(LayoutKind.Sequential)] diff --git a/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs b/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs new file mode 100644 index 000000000..f5f3e0b12 --- /dev/null +++ b/Dalamud.Bootstrap/OS/Windows/RelaxedProcessHandle.cs @@ -0,0 +1,36 @@ +using System; +using Dalamud.Bootstrap.OS.Windows.Raw; +using Microsoft.Win32.SafeHandles; + +namespace Dalamud.Bootstrap.Windows +{ + internal sealed class RelaxedProcessHandle : IDisposable + { + private SafeProcessHandle m_handle; + + private RelaxedProcessHandle(SafeProcessHandle handle) + { + m_handle = handle; + } + + public void Dispose() + { + + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static RelaxedProcessHandle Create(SafeProcessHandle handle, PROCESS_ACCESS_RIGHTS access) + { + + + return new RelaxedProcessHandle(handle); + } + } +} diff --git a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs index 443de8032..4e48b3b30 100644 --- a/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs +++ b/Dalamud.Bootstrap/SqexArg/ArgumentBuilder.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Text; @@ -23,7 +22,7 @@ namespace Dalamud.Bootstrap.SqexArg /// /// /// - public static ArgumentBuilder Parse(ReadOnlySpan argument) + public static ArgumentBuilder Parse(string argument) { return new ArgumentBuilder(ArgumentParser.Parse(argument)); } @@ -60,7 +59,7 @@ namespace Dalamud.Bootstrap.SqexArg var escapedKey = EscapeValue(key); var escapedvalue = EscapeValue(value); - buffer.Append($" /{escapedKey} ={escapedvalue}"); + buffer.Append($" /{escapedKey} ={escapedvalue}"); // TODO: this is broken } private static string EscapeValue(string value) diff --git a/Dalamud.Bootstrap/SqexArg/ArgumentParser.cs b/Dalamud.Bootstrap/SqexArg/ArgumentParser.cs index 7830ecfc5..4246a5f17 100644 --- a/Dalamud.Bootstrap/SqexArg/ArgumentParser.cs +++ b/Dalamud.Bootstrap/SqexArg/ArgumentParser.cs @@ -35,7 +35,7 @@ namespace Dalamud.Bootstrap.SqexArg /// /// /// Thrown when failed to parse the input. - public static IEnumerable> Parse(ReadOnlySpan input) + public static IEnumerable> Parse(string input) { var test = KeyMarker.Parse(input); var result = Parser.Parse(input);