diff --git a/PInvoke/NtDll/Winternl.cs b/PInvoke/NtDll/Winternl.cs
index 4b2a77a8..a5666191 100644
--- a/PInvoke/NtDll/Winternl.cs
+++ b/PInvoke/NtDll/Winternl.cs
@@ -300,142 +300,169 @@ namespace Vanara.PInvoke
[In] HPROCESS ParentProcess, [In] PROCESS_CREATE_FLAGS Flags, [In, Optional] IntPtr SectionHandle,
[In, Optional] IntPtr DebugPort, [In, Optional] IntPtr ExceptionPort, uint JobMemberLevel);
- ///
- ///
- /// [ NtQueryInformationProcess may be altered or unavailable in future versions of Windows. Applications should use the
- /// alternate functions listed in this topic.]
- ///
- /// Retrieves information about the specified process.
- ///
- /// A handle to the process for which information is to be retrieved.
- ///
- ///
- /// The type of process information to be retrieved. This parameter can be one of the following values from the
- /// PROCESSINFOCLASS enumeration.
- ///
- ///
- ///
- /// Value
- /// Meaning
- ///
- /// -
- /// ProcessBasicInformation
0
- ///
- /// Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a
- /// unique value used by the system to identify the specified process. Use the CheckRemoteDebuggerPresent and GetProcessId functions
- /// to obtain this information.
- ///
- ///
- /// -
- /// ProcessDebugPort
7
- ///
- /// Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process
- /// is being run under the control of a ring 3 debugger. Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function.
- ///
- ///
- /// -
- /// ProcessWow64Information
26
- ///
- /// Determines whether the process is running in the WOW64 environment (WOW64 is the x86 emulator that allows Win32-based
- /// applications to run on 64-bit Windows). Use the IsWow64Process2 function to obtain this information.
- ///
- ///
- /// -
- /// ProcessImageFileName
27
- ///
- /// Retrieves a UNICODE_STRING value containing the name of the image file for the process. Use the QueryFullProcessImageName or
- /// GetProcessImageFileName function to obtain this information.
- ///
- ///
- /// -
- /// ProcessBreakOnTermination
29
- /// Retrieves a ULONG value indicating whether the process is considered critical.
- ///
- /// -
- /// ProcessSubsystemInformation
75
- ///
- /// Retrieves a SUBSYSTEM_INFORMATION_TYPE value indicating the subsystem type of the process. The buffer pointed to by the
- /// ProcessInformation parameter should be large enough to hold a single SUBSYSTEM_INFORMATION_TYPE enumeration.
- ///
- ///
- ///
- ///
- ///
- ///
- /// A pointer to a buffer supplied by the calling application into which the function writes the requested information. The size of
- /// the information written varies depending on the data type of the ProcessInformationClass parameter:
- ///
- /// PROCESS_BASIC_INFORMATION
- ///
- /// When the ProcessInformationClass parameter is ProcessBasicInformation, the buffer pointed to by the ProcessInformation
- /// parameter should be large enough to hold a single PROCESS_BASIC_INFORMATION structure having the following layout:
- ///
- ///
- ///
- /// The UniqueProcessId member points to the system's unique identifier for this process. Use the GetProcessId function to
- /// retrieve this information.
- ///
- /// The PebBaseAddress member points to a PEB structure.
- /// The other members of this structure are reserved for internal use by the operating system.
- /// ULONG_PTR
- ///
- /// When the ProcessInformationClass parameter is ProcessWow64Information, the buffer pointed to by the ProcessInformation
- /// parameter should be large enough to hold a ULONG_PTR. If this value is nonzero, the process is running in a WOW64
- /// environment; otherwise, if the value is equal to zero, the process is not running in a WOW64 environment.
- ///
- /// Use the IsWow64Process2 function to determine whether a process is running in the WOW64 environment.
- /// UNICODE_STRING
- ///
- /// When the ProcessInformationClass parameter is ProcessImageFileName, the buffer pointed to by the ProcessInformation
- /// parameter should be large enough to hold a UNICODE_STRING structure as well as the string itself. The string stored in
- /// the Buffer member is the name of the image file.
- ///
- ///
- /// If the buffer is too small, the function fails with the STATUS_INFO_LENGTH_MISMATCH error code and the ReturnLength parameter is
- /// set to the required buffer size.
- ///
- ///
- /// The size of the buffer pointed to by the ProcessInformation parameter, in bytes.
- ///
- /// A pointer to a variable in which the function returns the size of the requested information. If the function was successful,
- /// this is the size of the information written to the buffer pointed to by the ProcessInformation parameter, but if the buffer was
- /// too small, this is the minimum size of buffer needed to receive the information successfully.
- ///
- ///
- /// The function returns an NTSTATUS success or error code.
- ///
- /// The forms and significance of NTSTATUS error codes are listed in the Ntstatus.h header file available in the DDK, and are
- /// described in the DDK documentation under Kernel-Mode Driver Architecture / Design Guide / Driver Programming Techniques /
- /// Logging Errors.
- ///
- ///
- ///
- ///
- /// The NtQueryInformationProcess function and the structures that it returns are internal to the operating system and
- /// subject to change from one release of Windows to another. To maintain the compatibility of your application, it is better to use
- /// public functions mentioned in the description of the ProcessInformationClass parameter instead.
- ///
- ///
- /// If you do use NtQueryInformationProcess, access the function through run-time dynamic linking. This gives your code an
- /// opportunity to respond gracefully if the function has been changed or removed from the operating system. Signature changes,
- /// however, may not be detectable.
- ///
- ///
- /// This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Ntdll.dll.
- ///
- ///
- // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess __kernel_entry NTSTATUS
- // NtQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN
- // ULONG ProcessInformationLength, OUT PULONG ReturnLength );
- [DllImport(Lib.NtDll, SetLastError = false, ExactSpelling = true)]
+ /// Set the debug object handle in the TEB. This function is UNDOCUMENTED.
+ /// Debug object handle. Retrieve from NtQueryInformationProcess
+ ///
+ /// The function returns an NTSTATUS success or error code.
+ ///
+ /// The forms and significance of NTSTATUS error codes are listed in the Ntstatus.h header file available in the DDK, and are
+ /// described in the DDK documentation under Kernel-Mode Driver Architecture / Design Guide / Driver Programming Techniques /
+ /// Logging Errors.
+ ///
+ ///
+ [DllImport(Lib.NtDll, SetLastError = false, ExactSpelling = true)]
+ public static extern NTStatus DbgUiSetThreadDebugObject(IntPtr DebugObjectHandle);
+
+ /// Call the kernel to remove the debug object. This function is UNDOCUMENTED.
+ /// The process handle.
+ /// Debug object handle. Retrieve from NtQueryInformationProcess
+ ///
+ /// The function returns an NTSTATUS success or error code.
+ ///
+ /// The forms and significance of NTSTATUS error codes are listed in the Ntstatus.h header file available in the DDK, and are
+ /// described in the DDK documentation under Kernel-Mode Driver Architecture / Design Guide / Driver Programming Techniques /
+ /// Logging Errors.
+ ///
+ ///
+ [DllImport(Lib.NtDll, SetLastError = false, ExactSpelling = true)]
+ public static extern NTStatus NtRemoveProcessDebug(HPROCESS ProcessHandle, IntPtr DebugObjectHandle);
+
+ ///
+ ///
+ /// [ NtQueryInformationProcess may be altered or unavailable in future versions of Windows. Applications should use the
+ /// alternate functions listed in this topic.]
+ ///
+ /// Retrieves information about the specified process.
+ ///
+ /// A handle to the process for which information is to be retrieved.
+ ///
+ ///
+ /// The type of process information to be retrieved. This parameter can be one of the following values from the
+ /// PROCESSINFOCLASS enumeration.
+ ///
+ ///
+ ///
+ /// Value
+ /// Meaning
+ ///
+ /// -
+ /// ProcessBasicInformation
0
+ ///
+ /// Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a
+ /// unique value used by the system to identify the specified process. Use the CheckRemoteDebuggerPresent and GetProcessId functions
+ /// to obtain this information.
+ ///
+ ///
+ /// -
+ /// ProcessDebugPort
7
+ ///
+ /// Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process
+ /// is being run under the control of a ring 3 debugger. Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function.
+ ///
+ ///
+ /// -
+ /// ProcessWow64Information
26
+ ///
+ /// Determines whether the process is running in the WOW64 environment (WOW64 is the x86 emulator that allows Win32-based
+ /// applications to run on 64-bit Windows). Use the IsWow64Process2 function to obtain this information.
+ ///
+ ///
+ /// -
+ /// ProcessImageFileName
27
+ ///
+ /// Retrieves a UNICODE_STRING value containing the name of the image file for the process. Use the QueryFullProcessImageName or
+ /// GetProcessImageFileName function to obtain this information.
+ ///
+ ///
+ /// -
+ /// ProcessBreakOnTermination
29
+ /// Retrieves a ULONG value indicating whether the process is considered critical.
+ ///
+ /// -
+ /// ProcessSubsystemInformation
75
+ ///
+ /// Retrieves a SUBSYSTEM_INFORMATION_TYPE value indicating the subsystem type of the process. The buffer pointed to by the
+ /// ProcessInformation parameter should be large enough to hold a single SUBSYSTEM_INFORMATION_TYPE enumeration.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// A pointer to a buffer supplied by the calling application into which the function writes the requested information. The size of
+ /// the information written varies depending on the data type of the ProcessInformationClass parameter:
+ ///
+ /// PROCESS_BASIC_INFORMATION
+ ///
+ /// When the ProcessInformationClass parameter is ProcessBasicInformation, the buffer pointed to by the ProcessInformation
+ /// parameter should be large enough to hold a single PROCESS_BASIC_INFORMATION structure having the following layout:
+ ///
+ ///
+ ///
+ /// The UniqueProcessId member points to the system's unique identifier for this process. Use the GetProcessId function to
+ /// retrieve this information.
+ ///
+ /// The PebBaseAddress member points to a PEB structure.
+ /// The other members of this structure are reserved for internal use by the operating system.
+ /// ULONG_PTR
+ ///
+ /// When the ProcessInformationClass parameter is ProcessWow64Information, the buffer pointed to by the ProcessInformation
+ /// parameter should be large enough to hold a ULONG_PTR. If this value is nonzero, the process is running in a WOW64
+ /// environment; otherwise, if the value is equal to zero, the process is not running in a WOW64 environment.
+ ///
+ /// Use the IsWow64Process2 function to determine whether a process is running in the WOW64 environment.
+ /// UNICODE_STRING
+ ///
+ /// When the ProcessInformationClass parameter is ProcessImageFileName, the buffer pointed to by the ProcessInformation
+ /// parameter should be large enough to hold a UNICODE_STRING structure as well as the string itself. The string stored in
+ /// the Buffer member is the name of the image file.
+ ///
+ ///
+ /// If the buffer is too small, the function fails with the STATUS_INFO_LENGTH_MISMATCH error code and the ReturnLength parameter is
+ /// set to the required buffer size.
+ ///
+ ///
+ /// The size of the buffer pointed to by the ProcessInformation parameter, in bytes.
+ ///
+ /// A pointer to a variable in which the function returns the size of the requested information. If the function was successful,
+ /// this is the size of the information written to the buffer pointed to by the ProcessInformation parameter, but if the buffer was
+ /// too small, this is the minimum size of buffer needed to receive the information successfully.
+ ///
+ ///
+ /// The function returns an NTSTATUS success or error code.
+ ///
+ /// The forms and significance of NTSTATUS error codes are listed in the Ntstatus.h header file available in the DDK, and are
+ /// described in the DDK documentation under Kernel-Mode Driver Architecture / Design Guide / Driver Programming Techniques /
+ /// Logging Errors.
+ ///
+ ///
+ ///
+ ///
+ /// The NtQueryInformationProcess function and the structures that it returns are internal to the operating system and
+ /// subject to change from one release of Windows to another. To maintain the compatibility of your application, it is better to use
+ /// public functions mentioned in the description of the ProcessInformationClass parameter instead.
+ ///
+ ///
+ /// If you do use NtQueryInformationProcess, access the function through run-time dynamic linking. This gives your code an
+ /// opportunity to respond gracefully if the function has been changed or removed from the operating system. Signature changes,
+ /// however, may not be detectable.
+ ///
+ ///
+ /// This function has no associated import library. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Ntdll.dll.
+ ///
+ ///
+ // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess __kernel_entry NTSTATUS
+ // NtQueryInformationProcess( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN
+ // ULONG ProcessInformationLength, OUT PULONG ReturnLength );
+ [DllImport(Lib.NtDll, SetLastError = false, ExactSpelling = true)]
[PInvokeData("winternl.h", MSDNShortId = "0eae7899-c40b-4a5f-9e9c-adae021885e7")]
public static extern NTStatus NtQueryInformationProcess([In] HPROCESS ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, [Out] IntPtr ProcessInformation, uint ProcessInformationLength, out uint ReturnLength);
diff --git a/UnitTests/PInvoke/NtDll/WinternlTests.cs b/UnitTests/PInvoke/NtDll/WinternlTests.cs
index 900239b1..2aa3cb4d 100644
--- a/UnitTests/PInvoke/NtDll/WinternlTests.cs
+++ b/UnitTests/PInvoke/NtDll/WinternlTests.cs
@@ -1,72 +1,128 @@
using NUnit.Framework;
-using NUnit.Framework.Constraints;
using System;
-using System.Diagnostics;
-using System.Linq;
using Vanara.Extensions;
-using Vanara.InteropServices;
using static Vanara.PInvoke.NtDll;
namespace Vanara.PInvoke.Tests
{
- [TestFixture]
- public partial class WinternlTests
- {
- [Test]
- public void NtQueryInformationProcessTest()
- {
- HPROCESS hProc = Kernel32.GetCurrentProcess();
- var procIsWow64 = hProc.IsWow64();
- var procIs64 = Environment.Is64BitProcess;
- var osIs64 = Environment.Is64BitOperatingSystem;
+ [TestFixture]
+ public partial class WinternlTests
+ {
+ [Test]
+ public void NtQueryInformationProcessTest()
+ {
+ HPROCESS hProc = Kernel32.GetCurrentProcess();
+ var procIsWow64 = hProc.IsWow64();
+ var procIs64 = Environment.Is64BitProcess;
+ var osIs64 = Environment.Is64BitOperatingSystem;
- using var pbi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessBasicInformation);
- Assert.That(pbi, ResultIs.ValidHandle);
- // Can get pointer here since PROCESS_BASIC_INFORMATION has no managed types
- unsafe
- {
- var rpbi = (PROCESS_BASIC_INFORMATION*)pbi;
- Assert.That(rpbi->UniqueProcessId.ToInt32(), Is.EqualTo(Kernel32.GetCurrentProcessId()));
- Assert.That(rpbi->PebBaseAddress, Is.Not.EqualTo(IntPtr.Zero));
- // Have to use ToStructure here since PEB has managed types
- var peb = rpbi->PebBaseAddress.ToStructure();
- // Have to use ToStructure here since RTL_USER_PROCESS_PARAMETERS has managed types
- var upp = peb.ProcessParameters.ToStructure();
- Assert.That(upp.CommandLine.ToString(hProc), Is.Not.Empty);
- TestContext.WriteLine($"Img: {upp.ImagePathName.ToString(hProc)}; CmdLine: {upp.CommandLine.ToString(hProc)}");
- }
+ using var pbi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessBasicInformation);
+ Assert.That(pbi, ResultIs.ValidHandle);
+ // Can get pointer here since PROCESS_BASIC_INFORMATION has no managed types
+ unsafe
+ {
+ var rpbi = (PROCESS_BASIC_INFORMATION*)pbi;
+ Assert.That(rpbi->UniqueProcessId.ToInt32(), Is.EqualTo(Kernel32.GetCurrentProcessId()));
+ Assert.That(rpbi->PebBaseAddress, Is.Not.EqualTo(IntPtr.Zero));
+ // Have to use ToStructure here since PEB has managed types
+ var peb = rpbi->PebBaseAddress.ToStructure();
+ // Have to use ToStructure here since RTL_USER_PROCESS_PARAMETERS has managed types
+ var upp = peb.ProcessParameters.ToStructure();
+ Assert.That(upp.CommandLine.ToString(hProc), Is.Not.Empty);
+ TestContext.WriteLine($"Img: {upp.ImagePathName.ToString(hProc)}; CmdLine: {upp.CommandLine.ToString(hProc)}");
+ }
- NtQueryResult pdp = null;
- Assert.That(() => pdp = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessDebugPort), Throws.Nothing);
- Assert.That(pdp, ResultIs.ValidHandle);
- TestContext.WriteLine($"DbgPort: {pdp.Value.ToInt64()}");
+ NtQueryResult pdp = null;
+ Assert.That(() => pdp = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessDebugPort), Throws.Nothing);
+ Assert.That(pdp, ResultIs.ValidHandle);
+ TestContext.WriteLine($"DbgPort: {pdp.Value.ToInt64()}");
- NtQueryResult pwi = null;
- Assert.That(() => pwi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessWow64Information), Throws.Nothing);
- Assert.That(pwi, ResultIs.ValidHandle);
- Assert.That(pwi.Value.Value, Is.True);
+ NtQueryResult pwi = null;
+ Assert.That(() => pwi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessWow64Information), Throws.Nothing);
+ Assert.That(pwi, ResultIs.ValidHandle);
+ Assert.That(pwi.Value.Value, Is.True);
- NtQueryResult pfn = null;
- Assert.That(() => pfn = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessImageFileName), Throws.Nothing);
- Assert.That(pfn, ResultIs.ValidHandle);
- TestContext.WriteLine($"Fn: {pfn.Value.ToString(hProc)}");
+ NtQueryResult pfn = null;
+ Assert.That(() => pfn = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessImageFileName), Throws.Nothing);
+ Assert.That(pfn, ResultIs.ValidHandle);
+ TestContext.WriteLine($"Fn: {pfn.Value.ToString(hProc)}");
- NtQueryResult pbt = null;
- Assert.That(() => pbt = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessBreakOnTermination), Throws.Nothing);
- Assert.That(pbt, ResultIs.ValidHandle);
- Assert.That(pbt.Value.Value, Is.False);
+ NtQueryResult pbt = null;
+ Assert.That(() => pbt = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessBreakOnTermination), Throws.Nothing);
+ Assert.That(pbt, ResultIs.ValidHandle);
+ Assert.That(pbt.Value.Value, Is.False);
- NtQueryResult psi = null;
- // This is documented, but fails on Win10
- Assert.That(() => psi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessSubsystemInformation), Throws.ArgumentException);
- //Assert.That(psi, ResultIs.ValidHandle);
- //Assert.That(Enum.IsDefined(typeof(SUBSYSTEM_INFORMATION_TYPE), psi.Value), Is.True);
- //TestContext.WriteLine($"SubSys: {psi.Value}");
+ NtQueryResult psi = null;
+ // This is documented, but fails on Win10
+ Assert.That(() => psi = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessSubsystemInformation), Throws.ArgumentException);
+ //Assert.That(psi, ResultIs.ValidHandle);
+ //Assert.That(Enum.IsDefined(typeof(SUBSYSTEM_INFORMATION_TYPE), psi.Value), Is.True);
+ //TestContext.WriteLine($"SubSys: {psi.Value}");
- // Try undocumented fetch
- NtQueryResult ppb = null;
- Assert.That(() => ppb = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessPriorityBoost), Throws.Nothing);
- TestContext.WriteLine($"Priority boost: {ppb.Value}");
- }
- }
+ // Try undocumented fetch
+ NtQueryResult ppb = null;
+ Assert.That(() => ppb = NtQueryInformationProcess(hProc, PROCESSINFOCLASS.ProcessPriorityBoost), Throws.Nothing);
+ TestContext.WriteLine($"Priority boost: {ppb.Value}");
+ }
+
+ [Test]
+ public void DbgUiSetThreadDebugObjectAndNtRemoveProcessDebugTest()
+ {
+ Kernel32.STARTUPINFO StartInfo = new Kernel32.STARTUPINFO
+ {
+ dwFlags = Kernel32.STARTF.STARTF_USESHOWWINDOW,
+ wShowWindow = (ushort)ShowWindowCommand.SW_HIDE
+ };
+
+ Assert.IsTrue(Kernel32.CreateProcess("notepad.exe", dwCreationFlags: Kernel32.CREATE_PROCESS.DEBUG_PROCESS | Kernel32.CREATE_PROCESS.CREATE_UNICODE_ENVIRONMENT, lpStartupInfo: StartInfo, lpProcessInformation: out Kernel32.SafePROCESS_INFORMATION Information));
+
+ using (Information)
+ using (NtQueryResult DebugObjectHandleQueryResult = NtQueryInformationProcess(Information.hProcess, PROCESSINFOCLASS.ProcessDebugObjectHandle))
+ {
+ Assert.That(DebugObjectHandleQueryResult, ResultIs.ValidHandle);
+ Assert.That(DebugObjectHandleQueryResult.Value, ResultIs.ValidHandle);
+
+ try
+ {
+ Assert.DoesNotThrow(() => DbgUiSetThreadDebugObject(DebugObjectHandleQueryResult.Value).ThrowIfFailed());
+
+ try
+ {
+ Kernel32.SafeHPROCESS DebugProcessHandle = Kernel32.SafeHPROCESS.Null;
+
+ try
+ {
+ while (true)
+ {
+ Assert.IsTrue(Kernel32.WaitForDebugEvent(out Kernel32.DEBUG_EVENT Event, Kernel32.INFINITE));
+
+ if (Event.dwDebugEventCode == Kernel32.DEBUG_EVENT_CODE.CREATE_PROCESS_DEBUG_EVENT)
+ {
+ DebugProcessHandle = new Kernel32.SafeHPROCESS(Event.u.CreateProcessInfo.hProcess);
+ break;
+ }
+
+ Assert.IsTrue(Kernel32.ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, Kernel32.DEBUG_CONTINUE.DBG_CONTINUE));
+ }
+
+ Assert.AreNotEqual(Kernel32.SafeHPROCESS.Null, DebugProcessHandle);
+ }
+ finally
+ {
+ DebugProcessHandle.Dispose();
+ }
+ }
+ finally
+ {
+ Assert.DoesNotThrow(() => DbgUiSetThreadDebugObject(IntPtr.Zero).ThrowIfFailed());
+ }
+ }
+ finally
+ {
+ Assert.IsTrue(Kernel32.TerminateProcess(Information.hProcess, 0));
+ Assert.DoesNotThrow(() => NtRemoveProcessDebug(Information.hProcess, DebugObjectHandleQueryResult.Value).ThrowIfFailed());
+ }
+ }
+ }
+ }
}
\ No newline at end of file