diff --git a/SystemInformer/SystemInformer.rc b/SystemInformer/SystemInformer.rc
index 08a52beb6e47..0f1b510f1c7f 100644
--- a/SystemInformer/SystemInformer.rc
+++ b/SystemInformer/SystemInformer.rc
@@ -26,18 +26,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
-1 TEXTINCLUDE
+1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
-2 TEXTINCLUDE
+2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"#include ""include/phappres.h""\0"
END
-3 TEXTINCLUDE
+3 TEXTINCLUDE
BEGIN
"\0"
END
@@ -622,6 +622,21 @@ BEGIN
COMBOBOX IDC_GROUPCPU,220,4,52,30,CBS_DROPDOWNLIST | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
END
+IDD_CPUSETS DIALOGEX 0, 0, 368, 244
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+CAPTION "CPU Sets"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "CPU Sets provide a way to declare CPU affinity in a soft manner that is compatible with OS power management, and to reaffinitize all background threads in the process to a subset of processors to avoid interference from OS threads within the process.",IDC_STATIC,6,6,339,23
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,6,35,357,174
+ PUSHBUTTON "Cancel",IDCANCEL,261,219,50,14
+ DEFPUSHBUTTON "OK",IDOK,314,219,50,14
+ PUSHBUTTON "Select all",IDC_SELECTALL,6,219,50,14
+ PUSHBUTTON "Unselect all",IDC_DESELECTALL,59,219,50,14
+ CONTROL "When all are unselected, all CPU Sets may be used",IDC_STATIC,
+ "Static",SS_SIMPLE | SS_WORDELLIPSIS | WS_GROUP,8,234,154,8
+END
+
IDD_SYSINFO DIALOGEX 0, 0, 423, 247
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
@@ -1873,6 +1888,11 @@ BEGIN
BOTTOMMARGIN, 220
END
+ IDD_CPUSETS, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 4
+ END
+
IDD_SYSINFO, DIALOG
BEGIN
END
@@ -2326,6 +2346,4 @@ IDB_SEARCH_ACTIVE_BMP BITMAP "resources\\active_search.bmp"
IDB_SEARCH_INACTIVE_BMP BITMAP "resources\\inactive_search.bmp"
#endif // English (United States) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
+/////////////////////////////////////////////////////////////////////////////
\ No newline at end of file
diff --git a/SystemInformer/SystemInformer.vcxproj b/SystemInformer/SystemInformer.vcxproj
index 6caab1c3b69e..c1e774274949 100644
--- a/SystemInformer/SystemInformer.vcxproj
+++ b/SystemInformer/SystemInformer.vcxproj
@@ -231,6 +231,7 @@
+
diff --git a/SystemInformer/SystemInformer.vcxproj.filters b/SystemInformer/SystemInformer.vcxproj.filters
index b2d852463fcc..989d9895c75a 100644
--- a/SystemInformer/SystemInformer.vcxproj.filters
+++ b/SystemInformer/SystemInformer.vcxproj.filters
@@ -342,6 +342,9 @@
System Informer
+
+ System Informer
+
System Informer
diff --git a/SystemInformer/cpuset.c b/SystemInformer/cpuset.c
new file mode 100644
index 000000000000..2847ead529ca
--- /dev/null
+++ b/SystemInformer/cpuset.c
@@ -0,0 +1,1197 @@
+/*
+ * Copyright (c) 2025 Winsider Seminars & Solutions, Inc. All rights reserved.
+ *
+ * This file is part of System Informer ("SI").
+ *
+ * Description:
+ *
+ * This module contains code to support CPU Sets in SI.
+ *
+ * Authors:
+ *
+ * wnstngs 2025
+ *
+ */
+
+//
+// The current version only supports modifying CPU Sets at the Process Default
+// level. TODO: Support the Thread Selected level.
+//
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+#include
+#include
+
+#include
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// Each CPU Set ID is encoded as follows:
+// Bits [31:16]: Processor group index
+// Bits [15:8]: Processor index within the group
+// Bits [7:0]: CPUSET_BIAS
+//
+#define CPUSET_BIAS 0x100
+#define CPUSET_GROUP_SHIFT 16
+#define CPUSET_ID_MASK 0xFF
+
+/**
+ * Encodes a CPU Set ID from a group index and a logical processor index.
+ *
+ * @param GroupIndex
+ * The index of the processor group.
+ *
+ * @param CpuIndex
+ * The index of the logical processor within the group.
+ *
+ * @return The encoded CPU Set ID.
+ */
+#define ENCODE_CPU_SET_ID(GroupIndex, CpuIndex) \
+ (((GroupIndex) << CPUSET_GROUP_SHIFT) | \
+ ((CpuIndex) & CPUSET_ID_MASK) | \
+ CPUSET_BIAS)
+
+/**
+ * Decodes a CPU Set ID to extract the processor group index.
+ *
+ * @param CpuSetId
+ * The encoded CPU Set ID.
+ *
+ * @return The processor group index.
+ */
+#define DECODE_CPU_SET_GROUP(CpuSetId) \
+ (((CpuSetId) - CPUSET_BIAS) >> CPUSET_GROUP_SHIFT)
+
+/**
+ * A helper which iterates through a list of CPU Set information structures
+ * and calls the specified callback for each CPU Set.
+ *
+ * @param InfoStart
+ * Pointer to the start of the CPU Set information buffer.
+ *
+ * @param Context
+ * Optional context pointer to pass to the callback function.
+ *
+ * @param Callback
+ * Callback function to invoke. If it fails, the enumeration stops.
+ * TBD: Add a parameter to control this behavior (break vs continue).
+ */
+#define FOREACH_CPU_SET_INFO(InfoStart, Context, Callback) \
+ do { \
+ PSYSTEM_CPU_SET_INFORMATION _info = (InfoStart); \
+ while (_info->Size != 0 && \
+ RTL_CONTAINS_FIELD(_info, _info->Size, CpuSet)) { \
+ if (_info->Type == CpuSetInformation) { \
+ if (!Callback(_info, (PVOID)(Context))) { \
+ break; \
+ } \
+ } \
+ _info = PTR_ADD_OFFSET(_info, _info->Size); \
+ } \
+ } while (0)
+
+typedef enum PH_CPU_SET_TYPE {
+ PhCpuSetTypeSystem,
+ PhCpuSetTypeProcessDefault,
+ PhCpuSetTypeThreadSelected
+} PH_CPU_SET_TYPE;
+
+typedef struct PHP_CPU_SETS_DLG_CTX {
+ HWND WindowHandle;
+ HWND ListViewHandle;
+ PPH_PROCESS_ITEM ProcessItem;
+ PH_CPU_SET_TYPE CpuSetType;
+ PULONG CpuSetIds;
+ ULONG CpuSetIdCount;
+} PHP_CPU_SETS_DLG_CTX, *PPHP_CPU_SETS_DLG_CTX;
+
+//
+// ----------------------------------------------------------------- Prototypes
+//
+
+INT_PTR
+CALLBACK
+PhpProcessCpuSetsDlgProc(
+ _In_ HWND DlgHandle,
+ _In_ UINT Message,
+ _In_ WPARAM WParam,
+ _In_ LPARAM LParam
+ );
+
+//
+// ------------------------------------------------------------------ Functions
+//
+
+//
+// CPU Set ID Helpers
+//
+
+/**
+ * Converts an array of CPU Set IDs into processor group affinity masks.
+ *
+ * @param CpuSetIds
+ * An array of CPU Set IDs to convert.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @param CpuSetMasks
+ * An output buffer for processor group affinity masks.
+ *
+ * @param GroupCount
+ * The number of processor groups.
+ *
+ * @return TRUE if successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpCpuSetIdsToMasks(
+ _In_reads_opt_(CpuSetIdCount) CONST ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount,
+ _Out_writes_(GroupCount) PKAFFINITY CpuSetMasks,
+ _In_ USHORT GroupCount
+ )
+{
+ ULONG i;
+ USHORT group;
+ UCHAR cpuIndex;
+
+ assert(CpuSetMasks != NULL);
+ assert(GroupCount > 0);
+
+ memset(CpuSetMasks, 0, GroupCount * sizeof(KAFFINITY));
+
+ for (i = 0; i < CpuSetIdCount; i += 1) {
+
+ //
+ // Decode group and processor index
+ //
+ cpuIndex = (UCHAR)CpuSetIds[i];
+ group = DECODE_CPU_SET_GROUP(CpuSetIds[i]);
+
+ assert(group < GroupCount);
+ assert(cpuIndex < MAXIMUM_PROC_PER_GROUP);
+
+ if (group >= GroupCount || cpuIndex >= MAXIMUM_PROC_PER_GROUP) {
+ return FALSE;
+ }
+
+ //
+ // Set the corresponding bit
+ //
+ CpuSetMasks[group] |= ((KAFFINITY)1 << cpuIndex);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Converts processor group affinity masks to CPU Set IDs.
+ *
+ * @param CpuSetMasks
+ * An array of processor group affinity masks to be converted.
+ *
+ * @param GroupCount
+ * The number of processor groups.
+ *
+ * @param IdList
+ * An optional output array where CPU Set IDs will be stored if provided.
+ *
+ * @param IdListSize
+ * The size of the IdList array.
+ *
+ * @param RequiredCount
+ * A pointer to a variable that will receive the number of IDs required to
+ * fully represent the input affinity masks.
+ *
+ * @return TRUE if successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpCpuSetMasksToIdList(
+ _In_reads_(GroupCount) CONST KAFFINITY *CpuSetMasks,
+ _In_ USHORT GroupCount,
+ _Out_writes_to_opt_(IdListSize, *RequiredCount) PULONG IdList,
+ _In_ ULONG IdListSize,
+ _Out_ PULONG RequiredCount
+ )
+{
+ ULONG total = 0;
+ ULONG bit;
+ USHORT group;
+ KAFFINITY mask;
+
+ assert(CpuSetMasks != NULL);
+ assert(GroupCount > 0);
+ assert(RequiredCount != NULL);
+
+ for (group = 0; group < GroupCount; group += 1) {
+ mask = CpuSetMasks[group];
+
+ while (mask) {
+ //
+ // Find the next set bit (logical processor)
+ //
+ BitScanForward64(&bit, mask);
+
+ //
+ // Clear the bit
+ //
+ mask &= ~(1ULL << bit);
+
+ //
+ // Encode the CPU Set ID if there's room
+ //
+ if (IdList && total < IdListSize) {
+ IdList[total] = ENCODE_CPU_SET_ID(group, (UCHAR)bit);
+ }
+
+ total += 1;
+ }
+ }
+
+ *RequiredCount = total;
+
+ if (total <= IdListSize) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+//
+// System CPU Sets
+//
+
+/**
+ * Internal helper function to query the available CPU Sets on the system.
+ *
+ * @param Information
+ * A pointer to a SYSTEM_CPU_SET_INFORMATION structure that receives
+ * the CPU Set data.
+ *
+ * @return An NTSTATUS error code.
+ */
+static
+NTSTATUS
+PhpQueryCpuSetInformation(
+ _Out_ PVOID *Information
+ )
+{
+ NTSTATUS status;
+ static ULONG initialBufferSize = 0;
+ HANDLE processHandle = NULL;
+ ULONG returnLength;
+ PSYSTEM_CPU_SET_INFORMATION cpuSetInfo;
+
+ *Information = NULL;
+
+ //
+ // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_cpu_set_information
+ // This is a variable-sized structure designed for future expansion. When iterating over
+ // this structure, use the size field to determine the offset to the next structure.
+ //
+ // N.B. The kernel returns the size even in the last element, we over-allocate by the
+ // Size offset and check it to minimize instructions (jxy-s).
+ //
+
+ if (initialBufferSize) {
+ returnLength = initialBufferSize;
+ cpuSetInfo = PhAllocateZero(returnLength);
+
+ if (!cpuSetInfo) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+ else {
+ returnLength = 0;
+ cpuSetInfo = NULL;
+ }
+
+ status = NtQuerySystemInformationEx(SystemCpuSetInformation,
+ &processHandle,
+ sizeof(HANDLE),
+ cpuSetInfo,
+ returnLength,
+ &returnLength);
+
+ if (status == STATUS_BUFFER_TOO_SMALL) {
+ returnLength += RTL_SIZEOF_THROUGH_FIELD(SYSTEM_CPU_SET_INFORMATION,
+ Size);
+
+ PhFree(cpuSetInfo);
+ cpuSetInfo = PhAllocateZero(returnLength);
+
+ if (!cpuSetInfo) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = NtQuerySystemInformationEx(SystemCpuSetInformation,
+ &processHandle,
+ sizeof(HANDLE),
+ cpuSetInfo,
+ returnLength,
+ NULL);
+
+ if (NT_SUCCESS(status) && initialBufferSize <= 0x100000) {
+ initialBufferSize = returnLength;
+ }
+ }
+
+ if (!NT_SUCCESS(status)) {
+ if (cpuSetInfo) {
+ PhFree(cpuSetInfo);
+ cpuSetInfo = NULL;
+ }
+ }
+
+ *Information = cpuSetInfo;
+
+ return status;
+}
+
+/**
+ * Queries the available CPU Sets on the system.
+ *
+ * @param Information
+ * A pointer to a SYSTEM_CPU_SET_INFORMATION structure that receives
+ * the CPU Set data.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhQuerySystemCpuSetInformation(
+ _Out_ PVOID *Information
+ )
+{
+ return PhpQueryCpuSetInformation(Information);
+}
+
+/**
+ * Enumerates all available CPU Sets on the system and calls the callback
+ * function for each CPU Set found.
+ *
+ * @param Callback
+ * A pointer to a callback function that is called for each CPU Set.
+ * If the callback returns FALSE, enumeration stops.
+ *
+ * @param Context
+ * An optional context pointer that is passed to the callback.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhEnumerateSystemCpuSets(
+ _In_ PPH_CPU_SETS_ENUMERATION_CALLBACK Callback,
+ _In_opt_ PVOID Context
+ )
+{
+ NTSTATUS status;
+ PVOID cpuSetInfo = NULL;
+
+ if (!Callback) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ status = PhpQueryCpuSetInformation(&cpuSetInfo);
+
+ if (NT_SUCCESS(status)) {
+ FOREACH_CPU_SET_INFO(cpuSetInfo, Context, Callback);
+ PhFree(cpuSetInfo);
+ }
+
+ return status;
+}
+
+//
+// Process Default CPU Sets
+//
+
+/**
+ * Queries the Process Default CPU Sets assigned to the given process.
+ *
+ * @param ProcessHandle
+ * A handle to the target process. The handle must have
+ * QUERY_LIMITED_INFORMATION permissions.
+ *
+ * @param CpuSetIds
+ * A pointer to an array of CPU Set IDs.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhQueryCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PULONG *CpuSetIds,
+ _Out_ PULONG CpuSetIdCount
+ )
+{
+ NTSTATUS status;
+ PKAFFINITY masks;
+ ULONG bufferSize;
+ static USHORT groupCount;
+ ULONG requiredCount;
+
+ groupCount = PhSystemProcessorInformation.NumberOfProcessorGroups;
+
+ *CpuSetIds = NULL;
+ *CpuSetIdCount = 0;
+
+ bufferSize = groupCount * sizeof(KAFFINITY);
+ masks = PhAllocateZero(bufferSize);
+
+ if (!masks) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = NtQueryInformationProcess(ProcessHandle,
+ ProcessDefaultCpuSetsInformation,
+ masks,
+ bufferSize,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(masks);
+ return status;
+ }
+
+ PhpCpuSetMasksToIdList(masks,
+ groupCount,
+ NULL,
+ 0,
+ &requiredCount);
+
+ if (requiredCount == 0) {
+ *CpuSetIds = NULL;
+ *CpuSetIdCount = 0;
+ PhFree(masks);
+ return STATUS_SUCCESS;
+ }
+
+ *CpuSetIds = PhAllocate(requiredCount * sizeof(**CpuSetIds));
+
+ if (!*CpuSetIds) {
+ PhFree(masks);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = PhpCpuSetMasksToIdList(masks,
+ groupCount,
+ *CpuSetIds,
+ requiredCount,
+ CpuSetIdCount)
+ ? STATUS_SUCCESS
+ : STATUS_BUFFER_TOO_SMALL;
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(*CpuSetIds);
+ *CpuSetIds = NULL;
+ }
+
+ PhFree(masks);
+
+ return status;
+}
+
+/**
+ * Sets the Process Default CPU Sets for a process.
+ *
+ * @param ProcessHandle
+ * A handle to the process. The handle must have
+ * PROCESS_SET_LIMITED_INFORMATION access.
+ *
+ * @param CpuSetIds
+ * An optional array of CPU Set IDs to assign to the process. If NULL or
+ * CpuSetIdCount is 0, the process will be assigned to all available
+ * CPU Sets.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhSetCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _In_reads_opt_(CpuSetIdCount) const ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount
+ )
+{
+ NTSTATUS status;
+ static USHORT groupCount;
+ PKAFFINITY masks;
+
+ groupCount = PhSystemProcessorInformation.NumberOfProcessorGroups;
+
+ masks = PhAllocateZero(groupCount * sizeof(KAFFINITY));
+ if (!masks) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (!PhpCpuSetIdsToMasks(CpuSetIds, CpuSetIdCount, masks, groupCount)) {
+ PhFree(masks);
+ return STATUS_CPU_SET_INVALID;
+ }
+
+ status = NtSetInformationProcess(ProcessHandle,
+ ProcessDefaultCpuSetsInformation,
+ masks,
+ groupCount * sizeof(KAFFINITY));
+
+ PhFree(masks);
+
+ return status;
+}
+
+//
+// Thread Selected CPU Sets
+// TODO: PhQueryCpuSetsThread()
+// TODO: PhSetCpuSetsThread()
+//
+
+//
+// CPU Sets Dialog
+//
+
+/**
+ * Shows the CPU Sets dialog for a process.
+ *
+ * @param ParentWindowHandle
+ * A handle to the parent window.
+ *
+ * @param ProcessItem
+ * A pointer to a process item. The dialog will display and allow
+ * modification of the CPU Sets for this process.
+ */
+VOID
+PhShowCpuSetsDialogProcess(
+ _In_ HWND ParentWindowHandle,
+ _In_ PPH_PROCESS_ITEM ProcessItem
+ )
+{
+ PPHP_CPU_SETS_DLG_CTX context;
+
+ context = PhAllocateZero(sizeof(PHP_CPU_SETS_DLG_CTX));
+ context->ProcessItem = ProcessItem;
+ context->CpuSetType = PhCpuSetTypeProcessDefault;
+
+ //
+ // We'll query the CPU Sets in WM_INITDIALOG
+ //
+ context->CpuSetIds = NULL;
+ context->CpuSetIdCount = 0;
+
+ PhDialogBox(PhInstanceHandle,
+ MAKEINTRESOURCE(IDD_CPUSETS),
+ ParentWindowHandle,
+ PhpProcessCpuSetsDlgProc,
+ context);
+
+ //
+ // Context is freed in WM_DESTROY
+ //
+}
+
+/**
+ * Callback function invoked for each System CPU Set from
+ * PhpPopulateListViewWithCpuSets().
+ * Adds detailed information about CPU Sets to a list view.
+ *
+ * @param Information
+ * A pointer to a structure representing a CPU Set.
+ *
+ * @param Context
+ * An optional pointer to a structure that provides the dialog context,
+ * including selected CPU Sets.
+ *
+ * @return TRUE if the operation was successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpPopulateCpuSetsListViewCallback(
+ _In_ PSYSTEM_CPU_SET_INFORMATION Information,
+ _In_opt_ PVOID Context
+ )
+{
+ PPHP_CPU_SETS_DLG_CTX context = (PPHP_CPU_SETS_DLG_CTX)Context;
+ INT lvItemIndex;
+ WCHAR number[PH_INT32_STR_LEN_1];
+ BOOLEAN isChecked = FALSE;
+ ULONG i;
+ ULONG low;
+ ULONG high;
+ ULONG mid;
+
+ assert(Information != NULL);
+
+ if (!context || !context->ListViewHandle) {
+ return FALSE;
+ }
+
+ //
+ // Determine if this CPU Set should be marked as checked
+ // by searching for its ID in the provided ID array.
+ //
+ if (context->CpuSetIds && context->CpuSetIdCount > 0) {
+#define BINARY_SEARCH_THRESHOLD 32
+ if (context->CpuSetIdCount > BINARY_SEARCH_THRESHOLD) {
+ low = 0;
+ high = context->CpuSetIdCount - 1;
+
+ while (low <= high) {
+ mid = low + (high - low) / 2;
+
+ if (context->CpuSetIds[mid] == Information->CpuSet.Id) {
+ isChecked = TRUE;
+ break;
+ }
+
+ if (context->CpuSetIds[mid] < Information->CpuSet.Id) {
+ low = mid + 1;
+ }
+ else {
+ high = mid - 1;
+ }
+ }
+ }
+ else {
+ for (i = 0; i < context->CpuSetIdCount; i += 1) {
+ if (context->CpuSetIds[i] == Information->CpuSet.Id) {
+ isChecked = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ PhPrintUInt32(number, Information->CpuSet.Id);
+
+ //
+ // Index = MAXINT because:
+ // "If this value [Index] is greater than the number of items currently
+ // contained by the listview control, the new item will be appended to
+ // the end of the list and assigned the correct index"
+ //
+ // Associate the CPU Set ID to the list item so that it can be retrieved
+ // in the selection handler
+ //
+ lvItemIndex = PhAddListViewItem(context->ListViewHandle,
+ MAXINT,
+ number,
+ UlongToPtr(Information->CpuSet.Id));
+
+ //
+ // Set the check state if this CPU Set is in the selection list
+ //
+ if (isChecked) {
+ assert(lvItemIndex >= 0);
+ ListView_SetCheckState(context->ListViewHandle, lvItemIndex, TRUE)
+ }
+
+ //
+ // Node
+ //
+ PhPrintUInt32(number, Information->CpuSet.NumaNodeIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 1, number);
+
+ //
+ // Group
+ //
+ PhPrintUInt32(number, Information->CpuSet.Group);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 2, number);
+
+ //
+ // Core
+ //
+ PhPrintUInt32(number, Information->CpuSet.CoreIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 3, number);
+
+ //
+ // Processor
+ //
+ PhPrintUInt32(number, Information->CpuSet.LogicalProcessorIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 4, number);
+
+ //
+ // Allocated
+ //
+ PhSetListViewSubItem(context->ListViewHandle,
+ lvItemIndex,
+ 5,
+ Information->CpuSet.Allocated ? L"Yes" : L"No");
+
+ return TRUE;
+}
+
+/**
+ * Adds CPU Set items to a list view control.
+ *
+ * @param ListViewHandle
+ * The handle to the list view control.
+ *
+ * @param DialogContext
+ * A pointer to a dialog context structure.
+ *
+ * @return STATUS_SUCCESS if no errors, or the specific error status code.
+ */
+static
+NTSTATUS
+PhpPopulateListViewWithCpuSets(
+ _In_ HWND ListViewHandle,
+ _In_ PPHP_CPU_SETS_DLG_CTX DialogContext
+ )
+{
+ if (!DialogContext || !DialogContext->ProcessItem) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ NTSTATUS status;
+ HANDLE processHandle;
+ PULONG cpuSetIds = NULL;
+ ULONG cpuSetIdCount = 0;
+ PVOID cpuSetsInfo = NULL;
+
+ status = PhOpenProcess(&processHandle,
+ PROCESS_QUERY_LIMITED_INFORMATION,
+ DialogContext->ProcessItem->ProcessId);
+
+ if (!NT_SUCCESS(status)) {
+ PhShowStatus(ListViewHandle,
+ L"Unable to query CPU Sets information",
+ status,
+ 0);
+ return status;
+ }
+
+ //
+ // Query Process Default CPU Sets
+ //
+ status = PhQueryCpuSetsProcess(processHandle,
+ &cpuSetIds,
+ &cpuSetIdCount);
+
+ NtClose(processHandle);
+
+ //
+ // Disable redraw while updating
+ //
+ ExtendedListView_SetRedraw(ListViewHandle, FALSE);
+
+ if (NT_SUCCESS(status) && cpuSetIdCount > 0) {
+ //
+ // On success with count > 0, we keep cpuSetIds and check those IDs
+ //
+ if (DialogContext->CpuSetIds) {
+ PhFree(DialogContext->CpuSetIds);
+ }
+ DialogContext->CpuSetIds = cpuSetIds;
+ DialogContext->CpuSetIdCount = cpuSetIdCount;
+
+ //
+ // Enumerate all system CPU Sets and check selected
+ //
+ status = PhEnumerateSystemCpuSets(PhpPopulateCpuSetsListViewCallback,
+ DialogContext);
+ }
+ else {
+ //
+ // Otherwise, free the allocated buffer
+ //
+ if (cpuSetIds) {
+ PhFree(cpuSetIds);
+ }
+ DialogContext->CpuSetIds = NULL;
+ DialogContext->CpuSetIdCount = 0;
+
+ //
+ // No Process Default CPU Set, show all System CPU Sets as unchecked
+ //
+ status = PhQuerySystemCpuSetInformation(&cpuSetsInfo);
+ if (NT_SUCCESS(status) && cpuSetsInfo) {
+ FOREACH_CPU_SET_INFO(cpuSetsInfo,
+ DialogContext,
+ PhpPopulateCpuSetsListViewCallback);
+ PhFree(cpuSetsInfo);
+ }
+
+ if (cpuSetIds) {
+ PhFree(cpuSetIds);
+ }
+ }
+
+ //
+ // Bring back redrawing
+ //
+ ExtendedListView_SetRedraw(ListViewHandle, TRUE);
+
+ return status;
+}
+
+/**
+ * Retrieves the selected CPU Set IDs from a ListView.
+ *
+ * @param ListViewHandle
+ * The handle to the ListView control.
+ *
+ * @param SelectedIds
+ * A pointer to store the dynamically allocated array of selected IDs.
+ * The caller must free this memory when it is no longer needed.
+ *
+ * @param SelectedCount
+ * Pointer to store the count of selected IDs.
+ *
+ * @return STATUS_SUCCESS if no errors, or the specific error status code.
+ */
+static
+NTSTATUS
+PhpGetSelectedCpuSetIds(
+ _In_ HWND ListViewHandle,
+ _Out_ PULONG *SelectedIds,
+ _Out_ PULONG SelectedCount
+)
+{
+ PVOID *paramArray;
+ PULONG ids;
+ ULONG count;
+ ULONG i;
+
+ if (!PhGetCheckedListViewItemParams(ListViewHandle,
+ ¶mArray,
+ &count)) {
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_NOT_FOUND;
+ }
+
+ //
+ // If no rows were checked, clean up and return empty.
+ //
+ if (count == 0) {
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_SUCCESS;
+ }
+
+ ids = PhAllocate(sizeof(*ids) * count);
+
+ if (!ids) {
+ PhFree(paramArray);
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ for (i = 0; i < count; i += 1) {
+ ids[i] = (ULONG)(ULONG_PTR)paramArray[i];
+ }
+
+ PhFree(paramArray);
+
+ *SelectedIds = ids;
+ *SelectedCount = count;
+
+ return STATUS_SUCCESS;
+}
+
+INT_PTR
+CALLBACK
+PhpProcessCpuSetsDlgProc(
+ _In_ HWND DlgHandle,
+ _In_ UINT Message,
+ _In_ WPARAM WParam,
+ _In_ LPARAM LParam
+)
+{
+ PPHP_CPU_SETS_DLG_CTX context;
+
+ if (Message == WM_INITDIALOG) {
+ context = (PPHP_CPU_SETS_DLG_CTX)LParam;
+
+ if (!context) {
+ return FALSE;
+ }
+
+ context->WindowHandle = DlgHandle;
+
+ PhSetWindowContext(DlgHandle,
+ PH_WINDOW_CONTEXT_DEFAULT,
+ context);
+ }
+ else {
+ context = PhGetWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+
+ if (!context && Message != WM_DESTROY) {
+ return FALSE;
+ }
+ }
+
+ switch (Message) {
+ case WM_INITDIALOG: {
+ NTSTATUS status;
+ PWSTR titleText;
+
+ context->WindowHandle = DlgHandle;
+ context->ListViewHandle = GetDlgItem(DlgHandle, IDC_LIST);
+
+ PhSetApplicationWindowIcon(DlgHandle);
+
+ //
+ // Set dialog title with process name if available
+ //
+ // @formatter:off
+ if (context->ProcessItem) {
+ titleText = PhaFormatString(L"CPU Sets - %s (%lu)",
+ context->ProcessItem->ProcessName->Buffer,
+ HandleToUlong(context->ProcessItem->ProcessId))->Buffer;
+
+ SetWindowText(DlgHandle, titleText);
+ }
+
+ PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 60, L"ID");
+ PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 60, L"Node");
+ PhAddListViewColumn(context->ListViewHandle, 2, 2, 2, LVCFMT_LEFT, 60, L"Group");
+ PhAddListViewColumn(context->ListViewHandle, 3, 3, 3, LVCFMT_LEFT, 60, L"Core");
+ PhAddListViewColumn(context->ListViewHandle, 4, 4, 4, LVCFMT_LEFT, 60, L"Processor");
+ PhAddListViewColumn(context->ListViewHandle, 5, 5, 5, LVCFMT_LEFT, 60, L"Allocated");
+ // @formatter:on
+
+ PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE);
+
+ PhSetControlTheme(context->ListViewHandle, L"explorer");
+
+ PhSetExtendedListView(context->ListViewHandle);
+
+ ListView_SetExtendedListViewStyleEx(context->ListViewHandle,
+ LVS_EX_CHECKBOXES,
+ LVS_EX_CHECKBOXES);
+
+ ExtendedListView_SetRedraw(context->ListViewHandle, FALSE);
+
+ ListView_DeleteAllItems(context->ListViewHandle);
+
+ status = PhpPopulateListViewWithCpuSets(context->ListViewHandle,
+ context);
+
+ ExtendedListView_SetRedraw(context->ListViewHandle, TRUE);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // If we couldn't query the CPU Sets, close the dialog
+ //
+ EndDialog(DlgHandle, IDCANCEL);
+ break;
+ }
+
+ //
+ // Enable/disable the OK button based on whether we have a process
+ //
+ EnableWindow(GetDlgItem(DlgHandle, IDOK), context->ProcessItem != NULL);
+
+ PhSetDialogFocus(DlgHandle, GetDlgItem(DlgHandle, IDOK));
+
+ PhInitializeWindowTheme(DlgHandle, PhEnableThemeSupport);
+ }
+ break;
+ case WM_DESTROY: {
+ PPHP_CPU_SETS_DLG_CTX destroyContext =
+ PhGetWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+
+ if (destroyContext) {
+ if (destroyContext->CpuSetIds) {
+ PhFree(destroyContext->CpuSetIds);
+ destroyContext->CpuSetIds = NULL;
+ destroyContext->CpuSetIdCount = 0;
+ }
+
+ PhRemoveWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+ PhFree(destroyContext);
+ }
+
+ return FALSE;
+ }
+ case WM_COMMAND: {
+ switch (GET_WM_COMMAND_ID(WParam, LParam)) {
+ case IDCANCEL:
+ EndDialog(DlgHandle, IDCANCEL);
+ break;
+ case IDC_SELECTALL:
+ case IDC_DESELECTALL: {
+ BOOL state = (GET_WM_COMMAND_ID(WParam, LParam) == IDC_SELECTALL);
+ HWND listView = context->ListViewHandle;
+ for (INT i = 0; i < ListView_GetItemCount(listView); i += 1) {
+ ListView_SetCheckState(listView, i, state)
+ }
+ break;
+ }
+ case IDOK: {
+ NTSTATUS status;
+ HANDLE processHandle = NULL;
+ PULONG selectedIds = NULL;
+ ULONG selectedCount = 0;
+ BOOLEAN succeeded = FALSE;
+
+ if (!context || !context->ListViewHandle) {
+ break;
+ }
+
+ //
+ // Get selected CPU Sets from the list view
+ //
+ status = PhpGetSelectedCpuSetIds(context->ListViewHandle,
+ &selectedIds,
+ &selectedCount);
+
+ if (!NT_SUCCESS(status)) {
+ PhShowStatus(DlgHandle,
+ L"Failed to retrieve selected CPU Set IDs.",
+ status,
+ 0);
+ break;
+ }
+
+ if (!selectedIds && selectedCount > 0) {
+ selectedCount = 0;
+ }
+
+ //
+ // Apply the selected CPU Sets to the process if we have a process
+ //
+ if (context->ProcessItem && context->ProcessItem->ProcessId) {
+ status = PhOpenProcess(&processHandle,
+ PROCESS_SET_LIMITED_INFORMATION |
+ PROCESS_QUERY_LIMITED_INFORMATION,
+ context->ProcessItem->ProcessId);
+ if (NT_SUCCESS(status)) {
+ //
+ // Set the process CPU Sets
+ //
+ status = PhSetCpuSetsProcess(processHandle,
+ selectedIds,
+ selectedCount);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Verify the changes were actually applied
+ //
+ PULONG verifyIds = NULL;
+ ULONG verifyCount = 0;
+
+ //
+ // Query the process again to verify our changes
+ //
+ status = PhQueryCpuSetsProcess(processHandle,
+ &verifyIds,
+ &verifyCount);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // If we selected CPU Sets but none were found on
+ // verification, something might have gone wrong
+ //
+ if (selectedCount > 0 && verifyCount == 0) {
+ succeeded = FALSE;
+ status = STATUS_UNSUCCESSFUL;
+ }
+ else {
+ succeeded = TRUE;
+ }
+
+ if (verifyIds) {
+ PhFree(verifyIds);
+ verifyIds = NULL;
+ }
+ }
+ else {
+ succeeded = FALSE;
+ }
+ }
+ else {
+ succeeded = FALSE;
+ }
+
+ NtClose(processHandle);
+ }
+
+ if (!succeeded) {
+ PhShowStatus(DlgHandle,
+ L"Unable to set CPU Sets for the process.",
+ status,
+ 0);
+
+ if (selectedIds) {
+ PhFree(selectedIds);
+ selectedIds = NULL;
+ }
+
+ break;
+ }
+ }
+ else {
+ //
+ // If we don't have a process to set CPU Set for,
+ // just consider it successful
+ //
+ succeeded = TRUE;
+ }
+
+ if (succeeded) {
+ //
+ // Free the previously selected IDs if any
+ //
+ if (context->CpuSetIds) {
+ PhFree(context->CpuSetIds);
+ }
+
+ //
+ // Update context with the newly selected IDs
+ //
+ context->CpuSetIds = selectedIds;
+ context->CpuSetIdCount = selectedCount;
+
+ EndDialog(DlgHandle, IDOK);
+ }
+ else {
+ //
+ // Clean up on failure
+ //
+ if (selectedIds) {
+ PhFree(selectedIds);
+ selectedIds = NULL;
+ }
+ }
+ }
+ break;
+ }
+ break;
+ case WM_CTLCOLORBTN:
+ return HANDLE_WM_CTLCOLORBTN(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ case WM_CTLCOLORDLG:
+ return HANDLE_WM_CTLCOLORDLG(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ case WM_CTLCOLORSTATIC:
+ return HANDLE_WM_CTLCOLORSTATIC(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ }
+ }
+
+ return FALSE;
+}
diff --git a/SystemInformer/include/phapp.h b/SystemInformer/include/phapp.h
index 03ced885d8f6..3baeff38a565 100644
--- a/SystemInformer/include/phapp.h
+++ b/SystemInformer/include/phapp.h
@@ -336,6 +336,13 @@ VOID PhShowProcessAffinityDialog(
_In_opt_ PPH_THREAD_ITEM ThreadItem
);
+// CPU Sets
+
+VOID PhShowCpuSetsDialogProcess(
+ _In_ HWND ParentWindowHandle,
+ _In_ PPH_PROCESS_ITEM ProcessItem
+ );
+
// begin_phapppub
_Success_(return)
PHAPPAPI
diff --git a/SystemInformer/include/sysinfop.h b/SystemInformer/include/sysinfop.h
index 301f82cbfeb8..f372aac3657f 100644
--- a/SystemInformer/include/sysinfop.h
+++ b/SystemInformer/include/sysinfop.h
@@ -358,6 +358,15 @@ BOOLEAN PhSipGetCpuFrequencyFromDistribution(
_Out_ DOUBLE *Frequency
);
+NTSTATUS PhSipQueryProcessorPerformanceDistribution(
+ _Out_ PVOID *Buffer
+ );
+
+NTSTATUS PhSipQueryProcessorPerformanceDistributionEx(
+ _In_ USHORT ProcessorGroup,
+ _Out_ PVOID* Buffer
+ );
+
PCPH_STRINGREF PhGetHybridProcessorType(
_In_ ULONG ProcessorIndex
);
diff --git a/SystemInformer/mainwnd.c b/SystemInformer/mainwnd.c
index 749df517d04c..e07e7742921d 100644
--- a/SystemInformer/mainwnd.c
+++ b/SystemInformer/mainwnd.c
@@ -2049,6 +2049,18 @@ VOID PhMwpOnCommand(
}
}
break;
+ case ID_PROCESS_CPU_SETS:
+ {
+ PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem();
+
+ if (processItem)
+ {
+ PhReferenceObject(processItem);
+ PhShowCpuSetsDialogProcess(WindowHandle, processItem);
+ PhDereferenceObject(processItem);
+ }
+ }
+ break;
case ID_MISCELLANEOUS_ACTIVITY:
{
PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem();
diff --git a/SystemInformer/mwpgproc.c b/SystemInformer/mwpgproc.c
index 44c1e3ef15e1..9a92ef7706c1 100644
--- a/SystemInformer/mwpgproc.c
+++ b/SystemInformer/mwpgproc.c
@@ -924,6 +924,9 @@ PPH_EMENU PhpCreateProcessMenu(
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_DEBUG, L"De&bug", NULL, NULL), ULONG_MAX);
PhInsertEMenuItem(menu, PhCreateEMenuSeparator(), ULONG_MAX);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_AFFINITY, L"&Affinity", NULL, NULL), ULONG_MAX);
+ if (WindowsVersion >= WINDOWS_10) {
+ PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_CPU_SETS, L"&CPU Sets", NULL, NULL), ULONG_MAX);
+ }
menuItem = PhCreateEMenuItem(0, ID_PROCESS_PRIORITYCLASS, L"&Priority", NULL, NULL);
PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, ID_PRIORITY_REALTIME, L"&Real time", NULL, NULL), ULONG_MAX);
diff --git a/SystemInformer/resource.h b/SystemInformer/resource.h
index 7aaa9b22b6b6..cd02d46ba82b 100644
--- a/SystemInformer/resource.h
+++ b/SystemInformer/resource.h
@@ -132,6 +132,7 @@
#define IDD_CHOOSENEW 275
#define IDD_MEMSTRINGSMINLEN 276
#define IDD_MEMSTRINGS 277
+#define IDD_CPUSETS 278
#define IDD_HNDLAUDITING 278
#define IDD_HNDLSECAUDIT 278
#define IDD_INFORMER 279
@@ -872,11 +873,12 @@
#define ID_NOTIFICATIONS_ENABLESINGLECLICKICONS 40312
#define ID_MISCELLANEOUS_EXECUTIONREQUIRED 40313
#define IDD_OBJAFDSOCKET 40314
-#define ID_TOOLS_COM_ACCESS_PERMISSIONS 40315
-#define ID_TOOLS_COM_ACCESS_RESTRICTIONS 40316
-#define ID_TOOLS_COM_LAUNCH_PERMISSIONS 40317
-#define ID_TOOLS_COM_LAUNCH_RESTRICTIONS 40318
-#define ID_TOOLS_INFORMER 40319
+#define ID_PROCESS_CPU_SETS 40315
+#define ID_TOOLS_COM_ACCESS_PERMISSIONS 40316
+#define ID_TOOLS_COM_ACCESS_RESTRICTIONS 40317
+#define ID_TOOLS_COM_LAUNCH_PERMISSIONS 40318
+#define ID_TOOLS_COM_LAUNCH_RESTRICTIONS 40319
+#define ID_TOOLS_INFORMER 40320
#define IDDYNAMIC 50000
#define IDPLUGINS 55000
diff --git a/SystemInformer/syssccpu.c b/SystemInformer/syssccpu.c
index febecf06ec1c..957e3e19d416 100644
--- a/SystemInformer/syssccpu.c
+++ b/SystemInformer/syssccpu.c
@@ -2084,12 +2084,14 @@ PCPH_STRINGREF PhGetHybridProcessorType(
*
* This function determines whether a specific logical processor in the system
* is parked, meaning it is temporarily unavailable for the thread scheduler.
- * It utilizes CPU sets functionality available starting with Windows 10.
+ * It uses CPU Sets functionality available starting with Windows 10.
*
* \param[in] ProcessorIndex The index of the logical processor to check.
*
* \return TRUE if the specified logical processor is parked, FALSE otherwise or
* in case of failure.
+ *
+ * \todo Use PhEnumerateSystemCpuSets()
*/
BOOLEAN PhIsCoreParked(
_In_ ULONG ProcessorIndex
diff --git a/phlib/guisuplistview.cpp b/phlib/guisuplistview.cpp
index 9868d656047f..0309fc38f84e 100644
--- a/phlib/guisuplistview.cpp
+++ b/phlib/guisuplistview.cpp
@@ -654,6 +654,56 @@ VOID PhGetSelectedIListViewItemParams(
*Items = static_cast(PhFinalArrayItems(&array));
}
+/*!
+ * Retrieves the lParam of each checked item in a checkbox-enabled ListView.
+ *
+ * @param ListViewHandle
+ * A handle to the ListView control (must have checkboxes enabled).
+ *
+ * @param Items
+ * Receives a pointer to an array of PVOID values, each corresponding
+ * to the lParam of a checked item.
+ *
+ * @param NumberOfItems
+ * Receives the number of entries in @p Items.
+ *
+ * @return TRUE if the operation succeeded, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+BOOLEAN PhGetCheckedListViewItemParams(
+ _In_ HWND ListViewHandle,
+ _Out_ PVOID **Items,
+ _Out_ PULONG NumberOfItems
+ )
+{
+ PH_ARRAY array;
+ INT totalItems, i;
+ PVOID param;
+
+ if (!ListViewHandle || !Items || !NumberOfItems)
+ {
+ return FALSE;
+ }
+
+ PhInitializeArray(&array, sizeof(PVOID), 4);
+
+ totalItems = ListView_GetItemCount(ListViewHandle);
+ for (i = 0; i < totalItems; i += 1)
+ {
+ if (ListView_GetCheckState(ListViewHandle, i))
+ {
+ if (PhGetListViewItemParam(ListViewHandle, i, ¶m))
+ {
+ PhAddItemArray(&array, ¶m);
+ }
+ }
+ }
+
+ *NumberOfItems = static_cast(PhFinalArrayCount(&array));
+ *Items = static_cast(PhFinalArrayItems(&array));
+ return TRUE;
+}
+
BOOLEAN PhGetIListViewClientRect(
_In_ IListView* ListView,
_Inout_ PRECT ClientRect
diff --git a/phlib/include/cpuset.h b/phlib/include/cpuset.h
new file mode 100644
index 000000000000..eb2330bb22ff
--- /dev/null
+++ b/phlib/include/cpuset.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2025 Winsider Seminars & Solutions, Inc. All rights reserved.
+ *
+ * This file is part of System Informer ("SI").
+ *
+ * Description:
+ *
+ * This module contains the public routine prototypes for CPU Sets in SI.
+ *
+ * Authors:
+ *
+ * wnstngs 2025
+ *
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef BOOLEAN (NTAPI *PPH_CPU_SETS_ENUMERATION_CALLBACK)(
+ _In_ PSYSTEM_CPU_SET_INFORMATION Information,
+ _In_opt_ PVOID Context
+ );
+
+NTSTATUS
+NTAPI
+PhQuerySystemCpuSetInformation(
+ _Out_ PVOID *Information
+ );
+
+NTSTATUS
+NTAPI
+PhEnumerateSystemCpuSets(
+ _In_ PPH_CPU_SETS_ENUMERATION_CALLBACK Callback,
+ _In_opt_ PVOID Context
+ );
+
+NTSTATUS
+NTAPI
+PhQueryCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PULONG *CpuSetIds,
+ _Out_ PULONG CpuSetIdCount
+ );
+
+NTSTATUS
+NTAPI
+PhSetCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _In_reads_opt_(CpuSetIdCount) const ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount
+ );
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/phlib/include/guisup.h b/phlib/include/guisup.h
index 8420098b3573..dc7026b35d6b 100644
--- a/phlib/include/guisup.h
+++ b/phlib/include/guisup.h
@@ -1281,6 +1281,42 @@ PhGetSelectedListViewItemParams(
_Out_ PULONG NumberOfItems
);
+PHLIBAPI
+VOID
+NTAPI
+PhGetSelectedIListViewItemParams(
+ _In_ IListView* ListView,
+ _Out_ PVOID** Items,
+ _Out_ PULONG NumberOfItems
+ );
+
+PHLIBAPI
+BOOLEAN
+NTAPI
+PhGetCheckedListViewItemParams(
+ _In_ HWND ListViewHandle,
+ _Out_ PVOID **Items,
+ _Out_ PULONG NumberOfItems
+ );
+
+PHLIBAPI
+BOOLEAN
+NTAPI
+PhGetIListViewClientRect(
+ _In_ IListView* ListView,
+ _Inout_ PRECT ClientRect
+ );
+
+PHLIBAPI
+BOOLEAN
+NTAPI
+PhGetIListViewItemRect(
+ _In_ IListView* ListView,
+ _In_ LONG StartIndex,
+ _In_ ULONG Flags,
+ _Inout_ PRECT ItemRect
+ );
+
FORCEINLINE
IListView*
NTAPI
diff --git a/phlib/phlib.vcxproj b/phlib/phlib.vcxproj
index ad815701d38b..f55a947f8fb4 100644
--- a/phlib/phlib.vcxproj
+++ b/phlib/phlib.vcxproj
@@ -230,6 +230,7 @@
+
diff --git a/phlib/phlib.vcxproj.filters b/phlib/phlib.vcxproj.filters
index 070d9e6f4343..159a23e0db48 100644
--- a/phlib/phlib.vcxproj.filters
+++ b/phlib/phlib.vcxproj.filters
@@ -466,5 +466,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file